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

release/2022-06-30

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2022-06-30 21:17:23 +00:00
32 changed files with 760 additions and 112 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -2063,6 +2063,32 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>validation</name>
<children>
<concept_node>
<name>atleastone</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>
</folder_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -8138,6 +8164,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>default_quote</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>default_received</name> <name>default_received</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -17779,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>
@@ -18959,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>
@@ -18980,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>
@@ -33967,6 +34077,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>is_quote</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>mark_as_received</name> <name>mark_as_received</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -8,6 +8,7 @@
"@asseinfo/react-kanban": "^2.2.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3", "@craco/craco": "^6.4.3",
"@fingerprintjs/fingerprintjs": "^3.3.3", "@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.1.1", "@sentry/react": "^7.1.1",
"@sentry/tracing": "^7.1.1", "@sentry/tracing": "^7.1.1",
"@splitsoftware/splitio-react": "^1.4.1", "@splitsoftware/splitio-react": "^1.4.1",

View File

@@ -144,6 +144,14 @@ function BillEnterModalContainer({
adjKeys.forEach((key) => { adjKeys.forEach((key) => {
newAdjustments[key] = newAdjustments[key] =
(newAdjustments[key] || 0) + adjustmentsToInsert[key]; (newAdjustments[key] || 0) + adjustmentsToInsert[key];
insertAuditTrail({
jobid: values.jobid,
operation: AuditTrailMapping.jobmodifylbradj({
mod_lbr_ty: key,
hours: adjustmentsToInsert[key].toFixed(1),
}),
});
}); });
const jobUpdate = client.mutate({ const jobUpdate = client.mutate({
@@ -161,10 +169,6 @@ function BillEnterModalContainer({
}); });
return; return;
} }
insertAuditTrail({
jobid: values.jobid,
operation: AuditTrailMapping.jobmodifylbradj(),
});
} }
const markPolReceived = const markPolReceived =

View File

@@ -552,7 +552,20 @@ export function BillEnterModalLinesComponent({
}); });
return ( return (
<Form.List name="billlines"> <Form.List
name="billlines"
rules={[
{
validator: async (_, billlines) => {
if (!billlines || billlines.length < 1) {
return Promise.reject(
new Error(t("billlines.validation.atleastone"))
);
}
},
},
]}
>
{(fields, { add, remove, move }) => { {(fields, { add, remove, move }) => {
return ( return (
<> <>

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,
@@ -175,7 +176,7 @@ export function JobLinesComponent({
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order, state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<> <JobLineConvertToLabor jobline={record} job={job}>
<CurrencyFormatter> <CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511" {record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m ? record.prt_dsmk_m
@@ -188,7 +189,7 @@ export function JobLinesComponent({
) : ( ) : (
<></> <></>
)} )}
</> </JobLineConvertToLabor>
), ),
}, },
{ {
@@ -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,9 @@ export function JobLinesComponent({
> >
<DeleteFilled /> <DeleteFilled />
</Button> </Button>
</Space> </>
)} )}
</div> </Space>
), ),
}, },
]; ];

View File

@@ -0,0 +1,252 @@
import { ClockCircleOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import {
Button,
Card,
Form,
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 _ from "lodash";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobLineConvertToLabor);
export function JobLineConvertToLabor({
children,
jobline,
job,
insertAuditTrail,
...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: job.id,
operation: AuditTrailMapping.jobmodifylbradj({
hours: calculateAdjustment({ mod_lbr_ty, job, jobline }).toFixed(1),
mod_lbr_ty,
}),
});
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 (
<>
{children}
{jobline.act_price !== 0 && (
<Popover
disabled={jobline.convertedtolbr}
content={overlay}
visible={visibility}
placement="bottom"
>
<Tooltip title={t("joblines.actions.converttolabor")}>
<Button
type="link"
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

@@ -217,21 +217,31 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<JobsRelatedRos jobid={job.id} job={job} /> <JobsRelatedRos jobid={job.id} job={job} />
</DataLabel> </DataLabel>
{job.vehicle && job.vehicle.notes && ( {job.vehicle && job.vehicle.notes && (
<DataLabel label={t("vehicles.fields.notes")}> <DataLabel
<span style={{ whiteSpace: "pre" }}>{job.vehicle.notes}</span> label={t("vehicles.fields.notes")}
valueStyle={{ whiteSpace: "pre-wrap" }}
>
{job.vehicle.notes}
</DataLabel>
)}
{job.vehicle && job.vehicle.v_paint_codes && (
<DataLabel
label={t("vehicles.fields.v_paint_codes", { number: "" })}
>
<span style={{ whiteSpace: "pre" }}>
{Object.keys(job.vehicle.v_paint_codes)
.filter(
(key) =>
job.vehicle.v_paint_codes[key] !== "" &&
job.vehicle.v_paint_codes[key] !== null &&
job.vehicle.v_paint_codes[key] !== undefined
)
.map((key, idx) => (
<Tag key={idx}>{job.vehicle.v_paint_codes[key]}</Tag>
))}
</span>
</DataLabel> </DataLabel>
)} )}
{
// job.vehicle && job.vehicle.v_paint_codes && (
// <DataLabel label={t("vehicles.fields.v_paint_codes")}>
// <span style={{ whiteSpace: "pre" }}>
// {Object.keys(job.vehicle.v_paint_codes).map((key, idx) => (
// <Tag key={idx}>{job.vehicle.v_paint_codes[key]}</Tag>
// ))}
// </span>
// </DataLabel>
// )
}
</div> </div>
</Card> </Card>
</Col> </Col>

View File

@@ -12,7 +12,24 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
export default function LaborAllocationsAdjustmentEdit({ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(LaborAllocationsAdjustmentEdit);
export function LaborAllocationsAdjustmentEdit({
insertAuditTrail,
jobId, jobId,
mod_lbr_ty, mod_lbr_ty,
adjustments, adjustments,
@@ -51,6 +68,15 @@ export default function LaborAllocationsAdjustmentEdit({
notification["success"]({ notification["success"]({
message: t("jobs.successes.save"), message: t("jobs.successes.save"),
}); });
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobmodifylbradj({
mod_lbr_ty: values.mod_lbr_ty,
hours:
values.hours -
((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1),
}),
});
} }
setLoading(false); setLoading(false);
setVisible(false); setVisible(false);

View File

@@ -124,6 +124,15 @@ export function PartsOrderModalComponent({
<Checkbox /> <Checkbox />
</Form.Item> </Form.Item>
)} )}
{OEConnection.treatment === "on" && !isReturn && (
<Form.Item
name="is_quote"
label={t("parts_orders.labels.is_quote")}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
)}
</LayoutFormRow> </LayoutFormRow>
<Divider orientation="left"> <Divider orientation="left">
{t("parts_orders.labels.inthisorder")} {t("parts_orders.labels.inthisorder")}
@@ -291,17 +300,32 @@ export function PartsOrderModalComponent({
<Input.TextArea rows={3} /> <Input.TextArea rows={3} />
</Form.Item> </Form.Item>
<Radio.Group <Form.Item noStyle shouldUpdate>
defaultValue={sendType} {() => {
onChange={(e) => setSendType(e.target.value)} const is_quote = form.getFieldValue("is_quote");
> if (is_quote) setSendType("oec");
<Radio value={"none"}>{t("general.labels.none")}</Radio> return (
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio> <Radio.Group
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio> defaultValue={sendType}
{OEConnection.treatment === "on" && !isReturn && ( value={sendType}
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio> onChange={(e) => setSendType(e.target.value)}
)} >
</Radio.Group> <Radio disabled={is_quote} value={"none"}>
{t("general.labels.none")}
</Radio>
<Radio disabled={is_quote} value={"e"}>
{t("parts_orders.labels.email")}
</Radio>
<Radio disabled={is_quote} value={"p"}>
{t("parts_orders.labels.print")}
</Radio>
{OEConnection.treatment === "on" && !isReturn && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>
);
}}
</Form.Item>
</div> </div>
); );
} }

View File

@@ -93,31 +93,53 @@ export function PartsOrderModalContainer({
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS); const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async ({ removefrompartsqueue, ...values }) => { const handleFinish = async ({
removefrompartsqueue,
is_quote,
...values
}) => {
logImEXEvent("parts_order_insert"); logImEXEvent("parts_order_insert");
setSaving(true); setSaving(true);
const insertResult = await insertPartOrder({ let insertResult;
variables: { if (!is_quote) {
po: [ insertResult = await insertPartOrder({
{ variables: {
...values, po: [
order_date: moment().format("YYYY-MM-DD"), {
orderedby: currentUser.email, ...values,
jobid: jobId, order_date: moment().format("YYYY-MM-DD"),
user_email: currentUser.email, orderedby: currentUser.email,
return: isReturn, jobid: jobId,
status: bodyshop.md_order_statuses.default_ordered || "Ordered*", user_email: currentUser.email,
}, return: isReturn,
], status: bodyshop.md_order_statuses.default_ordered || "Ordered*",
}, },
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"], ],
}); },
if (!!insertResult.error) { refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
notification["error"]({ });
message: t("parts_orders.errors.creating"), if (!!insertResult.error) {
description: JSON.stringify(insertResult.error), notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.error),
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
}); });
return;
} }
const jobLinesResult = await updateJobLines({ const jobLinesResult = await updateJobLines({
@@ -127,6 +149,8 @@ export function PartsOrderModalContainer({
.map((item) => item.job_line_id), .map((item) => item.job_line_id),
status: isReturn status: isReturn
? bodyshop.md_order_statuses.default_returned || "Returned*" ? bodyshop.md_order_statuses.default_returned || "Returned*"
: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*", : bodyshop.md_order_statuses.default_ordered || "Ordered*",
}, },
}); });
@@ -142,17 +166,6 @@ export function PartsOrderModalContainer({
}); });
} }
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
if (!!jobLinesResult.errors) { if (!!jobLinesResult.errors) {
notification["error"]({ notification["error"]({
message: t("parts_orders.errors.creating"), message: t("parts_orders.errors.creating"),
@@ -160,12 +173,6 @@ export function PartsOrderModalContainer({
}); });
} }
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
if (values.vendorid === bodyshop.inhousevendorid) { if (values.vendorid === bodyshop.inhousevendorid) {
setBillEnterContext({ setBillEnterContext({
actions: { refetch: refetch }, actions: { refetch: refetch },

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

@@ -15,6 +15,7 @@ import {
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component"; import ScheduleBlockDay from "../schedule-block-day/schedule-block-day.component";
import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component"; import ScheduleCalendarHeaderGraph from "./schedule-calendar-header-graph.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -66,6 +67,9 @@ export function ScheduleCalendarHeaderComponent({
<td> <td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
</td> </td>
<td>
<OwnerNameDisplay ownerObject={j} />
</td>
<td> <td>
{`(${( {`(${(
j.labhrs.aggregate.sum.mod_lb_hrs + j.labhrs.aggregate.sum.mod_lb_hrs +
@@ -99,6 +103,9 @@ export function ScheduleCalendarHeaderComponent({
<td> <td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
</td> </td>
<td>
<OwnerNameDisplay ownerObject={j} />
</td>
<td> <td>
{`(${( {`(${(
j.labhrs.aggregate.sum.mod_lb_hrs + j.labhrs.aggregate.sum.mod_lb_hrs +

View File

@@ -2,9 +2,28 @@ import { Form, Input } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function ShopInfoOrderStatusComponent({ form }) { import { connect } from "react-redux";
const { t } = useTranslation(); import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ShopInfoOrderStatusComponent);
export function ShopInfoOrderStatusComponent({ bodyshop, form }) {
const { t } = useTranslation();
const { OEConnection } = useTreatments(
["OEConnection"],
{},
bodyshop.imexshopid
);
return ( return (
<LayoutFormRow header={t("bodyshop.labels.orderstatuses")}> <LayoutFormRow header={t("bodyshop.labels.orderstatuses")}>
<Form.Item <Form.Item
@@ -56,6 +75,20 @@ export default function ShopInfoOrderStatusComponent({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
{OEConnection.treatment === "on" && (
<Form.Item
label={t("bodyshop.fields.statuses.default_quote")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_order_statuses", "default_quote"]}
>
<Input />
</Form.Item>
)}
</LayoutFormRow> </LayoutFormRow>
); );
} }

View File

@@ -324,6 +324,9 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
ro_number ro_number
scheduled_completion scheduled_completion
actual_completion actual_completion
ownr_fn
ownr_ln
ownr_co_nm
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) { ) {
@@ -352,6 +355,9 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
id id
scheduled_in scheduled_in
ro_number ro_number
ownr_fn
ownr_ln
ownr_co_nm
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) { ) {

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

@@ -17,6 +17,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -31,6 +32,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
statusFilters, statusFilters,
} = searchParams; } = searchParams;
const history = useHistory(); const history = useHistory();
const [filter, setFilter] = useLocalStorage("filter_parts_queue", null);
const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, { const { loading, error, data, refetch } = useQuery(QUERY_PARTS_QUEUE, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -92,7 +94,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
// searchParams.page = pagination.current; // searchParams.page = pagination.current;
searchParams.sortcolumn = sorter.columnKey; searchParams.sortcolumn = sorter.columnKey;
searchParams.sortorder = sorter.order; searchParams.sortorder = sorter.order;
setFilter(filters);
history.push({ search: queryString.stringify(searchParams) }); history.push({ search: queryString.stringify(searchParams) });
}; };
@@ -247,6 +249,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
key: "queued_for_parts", key: "queued_for_parts",
sorter: (a, b) => a.queued_for_parts - b.queued_for_parts, sorter: (a, b) => a.queued_for_parts - b.queued_for_parts,
sortOrder: sortcolumn === "queued_for_parts" && sortorder, sortOrder: sortcolumn === "queued_for_parts" && sortorder,
filteredValue: filter?.queued_for_parts || null,
filters: [ filters: [
{ {
text: "Queued", text: "Queued",

View File

@@ -103,7 +103,7 @@
"jobimported": "Job imported.", "jobimported": "Job imported.",
"jobinproductionchange": "Job production status set to {{inproduction}}", "jobinproductionchange": "Job production status set to {{inproduction}}",
"jobioucreated": "IOU Created.", "jobioucreated": "IOU Created.",
"jobmodifylbradj": "Labor adjustments modified.", "jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
"jobnoteadded": "Note added to job.", "jobnoteadded": "Note added to job.",
"jobnotedeleted": "Note deleted from job.", "jobnotedeleted": "Note deleted from job.",
"jobnoteupdated": "Note updated on job.", "jobnoteupdated": "Note updated on job.",
@@ -136,6 +136,9 @@
"other": "-- Not On Estimate --", "other": "-- Not On Estimate --",
"reconciled": "Reconciled!", "reconciled": "Reconciled!",
"unreconciled": "Unreconciled" "unreconciled": "Unreconciled"
},
"validation": {
"atleastone": "At least one bill line must be entered."
} }
}, },
"bills": { "bills": {
@@ -502,6 +505,7 @@
"default_imported": "Default Imported Status", "default_imported": "Default Imported Status",
"default_invoiced": "Default Invoiced Status", "default_invoiced": "Default Invoiced Status",
"default_ordered": "Default Ordered Status", "default_ordered": "Default Ordered Status",
"default_quote": "Default Quote Status",
"default_received": "Default Received Status", "default_received": "Default Received Status",
"default_returned": "Default Returned", "default_returned": "Default Returned",
"default_scheduled": "Default Scheduled Status", "default_scheduled": "Default Scheduled Status",
@@ -1110,6 +1114,7 @@
}, },
"joblines": { "joblines": {
"actions": { "actions": {
"converttolabor": "Convert amount to Labor.",
"new": "New Line" "new": "New Line"
}, },
"errors": { "errors": {
@@ -1175,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",
@@ -2013,6 +2020,7 @@
"confirmdelete": "Are you sure you want to delete this item? It cannot be recovered. Job line statuses will not be updated and may require manual review. ", "confirmdelete": "Are you sure you want to delete this item? It cannot be recovered. Job line statuses will not be updated and may require manual review. ",
"email": "Send by Email", "email": "Send by Email",
"inthisorder": "Parts in this Order", "inthisorder": "Parts in this Order",
"is_quote": "Parts Quote?",
"mark_as_received": "Mark as Received?", "mark_as_received": "Mark as Received?",
"newpartsorder": "New Parts Order", "newpartsorder": "New Parts Order",
"notyetordered": "This part has not yet been ordered.", "notyetordered": "This part has not yet been ordered.",

View File

@@ -136,6 +136,9 @@
"other": "", "other": "",
"reconciled": "", "reconciled": "",
"unreconciled": "" "unreconciled": ""
},
"validation": {
"atleastone": ""
} }
}, },
"bills": { "bills": {
@@ -502,6 +505,7 @@
"default_imported": "", "default_imported": "",
"default_invoiced": "", "default_invoiced": "",
"default_ordered": "", "default_ordered": "",
"default_quote": "",
"default_received": "", "default_received": "",
"default_returned": "", "default_returned": "",
"default_scheduled": "", "default_scheduled": "",
@@ -1110,6 +1114,7 @@
}, },
"joblines": { "joblines": {
"actions": { "actions": {
"converttolabor": "",
"new": "" "new": ""
}, },
"errors": { "errors": {
@@ -1175,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",
@@ -2013,6 +2020,7 @@
"confirmdelete": "", "confirmdelete": "",
"email": "Enviar por correo electrónico", "email": "Enviar por correo electrónico",
"inthisorder": "Partes en este pedido", "inthisorder": "Partes en este pedido",
"is_quote": "",
"mark_as_received": "", "mark_as_received": "",
"newpartsorder": "", "newpartsorder": "",
"notyetordered": "", "notyetordered": "",

View File

@@ -136,6 +136,9 @@
"other": "", "other": "",
"reconciled": "", "reconciled": "",
"unreconciled": "" "unreconciled": ""
},
"validation": {
"atleastone": ""
} }
}, },
"bills": { "bills": {
@@ -502,6 +505,7 @@
"default_imported": "", "default_imported": "",
"default_invoiced": "", "default_invoiced": "",
"default_ordered": "", "default_ordered": "",
"default_quote": "",
"default_received": "", "default_received": "",
"default_returned": "", "default_returned": "",
"default_scheduled": "", "default_scheduled": "",
@@ -1110,6 +1114,7 @@
}, },
"joblines": { "joblines": {
"actions": { "actions": {
"converttolabor": "",
"new": "" "new": ""
}, },
"errors": { "errors": {
@@ -1175,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",
@@ -2013,6 +2020,7 @@
"confirmdelete": "", "confirmdelete": "",
"email": "Envoyé par email", "email": "Envoyé par email",
"inthisorder": "Pièces dans cette commande", "inthisorder": "Pièces dans cette commande",
"is_quote": "",
"mark_as_received": "", "mark_as_received": "",
"newpartsorder": "", "newpartsorder": "",
"notyetordered": "", "notyetordered": "",

View File

@@ -18,7 +18,8 @@ const AuditTrailMapping = {
i18n.t("audit_trail.messages.jobspartsorder", { order_number }), i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
jobspartsreturn: (order_number) => jobspartsreturn: (order_number) =>
i18n.t("audit_trail.messages.jobspartsreturn", { order_number }), i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
jobmodifylbradj: () => i18n.t("audit_trail.messages.jobmodifylbradj", {}), jobmodifylbradj: ({ mod_lbr_ty, hours }) =>
i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
billposted: (invoice_number) => billposted: (invoice_number) =>
i18n.t("audit_trail.messages.billposted", { invoice_number }), i18n.t("audit_trail.messages.billposted", { invoice_number }),
billupdated: (invoice_number) => billupdated: (invoice_number) =>

View File

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
import { notification } from "antd"; import { notification } from "antd";
import axios from "axios"; import axios from "axios";
import jsreport from "jsreport-browser-client-dist"; import jsreport from "@jsreport/browser-client";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";
@@ -9,7 +9,9 @@ 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";
const server = process.env.REACT_APP_REPORTS_SERVER_URL; const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server; jsreport.serverUrl = server;
const Templates = TemplateList(); const Templates = TemplateList();
@@ -21,6 +23,10 @@ export default async function RenderTemplate(
renderAsExcel = false, renderAsExcel = false,
renderAsText = false renderAsText = false
) { ) {
if (window.jsr3) {
jsreport.serverUrl = "https://reports3.test.imex.online/";
}
//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
@@ -69,7 +75,7 @@ export default async function RenderTemplate(
}; };
try { try {
const render = await jsreport.renderAsync(reportRequest); const render = await jsreport.render(reportRequest);
if (!renderAsHtml) { if (!renderAsHtml) {
render.download( render.download(
@@ -104,16 +110,17 @@ export default async function RenderTemplate(
}, },
}; };
const pdfRender = await jsreport.renderAsync(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,
}); });
}); });
} }
@@ -152,6 +159,9 @@ export async function RenderTemplates(
// (template) => template.name === item.templateObject.name // (template) => template.name === item.templateObject.name
// ); // );
// }); // });
if (window.jsr3) {
jsreport.serverUrl = "https://reports3.test.imex.online/";
}
unsortedTemplatesAndData.sort(function (a, b) { unsortedTemplatesAndData.sort(function (a, b) {
return ( return (
@@ -242,13 +252,11 @@ export async function RenderTemplates(
}; };
try { try {
const render = await jsreport.renderAsync(reportRequest); const render = await jsreport.render(reportRequest);
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

@@ -23,9 +23,9 @@ export default async function FcmHandler({ client, payload }) {
updated_at(oldupdated0) { updated_at(oldupdated0) {
return new Date(); return new Date();
}, },
messages_aggregate(cached) { // messages_aggregate(cached) {
return { aggregate: { count: cached.aggregate.count + 1 } }; // return { aggregate: { count: cached.aggregate.count + 1 } };
}, // },
}, },
}); });
break; break;

View File

@@ -0,0 +1,40 @@
import { useState } from "react";
export default function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
if (typeof window === "undefined") {
return initialValue;
}
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
if (typeof window !== "undefined") {
window.localStorage.setItem(key, JSON.stringify(valueToStore));
}
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}

View File

@@ -1918,6 +1918,11 @@
"@types/yargs" "^16.0.0" "@types/yargs" "^16.0.0"
chalk "^4.0.0" chalk "^4.0.0"
"@jsreport/browser-client@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jsreport/browser-client/-/browser-client-3.1.0.tgz#a84011087ca8a29a6dc6a852fa05ffaf1983a679"
integrity sha512-ZElwn2KRIzkUzAyD5UKGxULZUhokWuPOlMzrmiur4WirqH3yoiHlOJEdnRGkjjE/fhZzCR8gBFZ/TuOW/fsOIw==
"@nodelib/fs.scandir@2.1.5": "@nodelib/fs.scandir@2.1.5":
version "2.1.5" version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"

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

@@ -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,
}); });
} }
}; };