Merged in development (pull request #32)

SIT 1 Bug Fixes
Package updates.
IO-723
IO-723
IO-744
IO-746
IO-743
IO-745
IO-747
This commit is contained in:
Patrick Fic
2021-03-09 01:08:54 +00:00
37 changed files with 31628 additions and 844 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
@@ -20710,6 +20710,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>voidnote</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>

23348
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
"@stripe/stripe-js": "^1.12.1", "@stripe/stripe-js": "^1.12.1",
"@tanem/react-nprogress": "^3.0.57", "@tanem/react-nprogress": "^3.0.57",
"@tinymce/tinymce-react": "^3.10.3", "@tinymce/tinymce-react": "^3.10.3",
"antd": "^4.12.3", "antd": "^4.13.1",
"apollo-link-logger": "^2.0.0", "apollo-link-logger": "^2.0.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"craco-less": "^1.17.1", "craco-less": "^1.17.1",
@@ -26,7 +26,7 @@
"i18next-browser-languagedetector": "^6.0.1", "i18next-browser-languagedetector": "^6.0.1",
"jsoneditor": "^9.1.10", "jsoneditor": "^9.1.10",
"jsreport-browser-client-dist": "^1.3.0", "jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.11", "libphonenumber-js": "^1.9.12",
"logrocket": "^1.0.13", "logrocket": "^1.0.13",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"phone": "^2.4.21", "phone": "^2.4.21",
@@ -39,11 +39,11 @@
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-drag-listview": "^0.1.8", "react-drag-listview": "^0.1.8",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-i18next": "^11.8.7", "react-i18next": "^11.8.9",
"react-icons": "^4.2.0", "react-icons": "^4.2.0",
"react-number-format": "^4.4.4", "react-number-format": "^4.4.4",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-resizable": "^1.11.0", "react-resizable": "^1.11.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",

View File

@@ -117,6 +117,13 @@ export default function ContractsCarsComponent({
type: "radio", type: "radio",
selectedRowKeys: [selectedCar], selectedRowKeys: [selectedCar],
}} }}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleSelect(record);
},
};
}}
/> />
); );
} }

View File

@@ -1,9 +1,9 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import moment from "moment";
import React from "react"; import React from "react";
import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries"; import { QUERY_AVAILABLE_CC } from "../../graphql/courtesy-car.queries";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ContractCarsComponent from "./contract-cars.component"; import ContractCarsComponent from "./contract-cars.component";
import moment from "moment";
export default function ContractCarsContainer({ selectedCarState, form }) { export default function ContractCarsContainer({ selectedCarState, form }) {
const { loading, error, data } = useQuery(QUERY_AVAILABLE_CC, { const { loading, error, data } = useQuery(QUERY_AVAILABLE_CC, {
@@ -14,6 +14,11 @@ export default function ContractCarsContainer({ selectedCarState, form }) {
const handleSelect = (record) => { const handleSelect = (record) => {
setSelectedCar(record.id); setSelectedCar(record.id);
console.log(
"🚀 ~ file: contract-cars.container.jsx ~ line 19 ~ record",
record
);
form.setFieldsValue({ form.setFieldsValue({
kmstart: record.mileage, kmstart: record.mileage,
dailyrate: record.dailycost, dailyrate: record.dailycost,

View File

@@ -1,25 +1,26 @@
import React, { useState } from "react"; import { useMutation } from "@apollo/client";
import { import {
Button, Button,
Form,
InputNumber,
notification, notification,
Popover, Popover,
Radio, Radio,
Form, Select,
InputNumber,
Space, Space,
} from "antd"; } from "antd";
import { useTranslation } from "react-i18next"; import axios from "axios";
import { useMutation } from "@apollo/client";
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
import moment from "moment"; import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { INSERT_NEW_JOB } from "../../graphql/jobs.queries";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { useHistory } from "react-router-dom";
import axios from "axios";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -49,8 +50,9 @@ export function ContractConvertToRo({
moment(contract.start), moment(contract.start),
"days" "days"
); );
const billingLines = [ const billingLines = [];
{ if (contractLength > 0)
billingLines.push({
unq_seq: 1, unq_seq: 1,
line_no: 1, line_no: 1,
line_ref: 1, line_ref: 1,
@@ -63,9 +65,7 @@ export function ContractConvertToRo({
mod_lb_hrs: 0, mod_lb_hrs: 0,
db_ref: "io-ccdr", db_ref: "io-ccdr",
// mod_lbr_ty: "PAL", // mod_lbr_ty: "PAL",
}, });
];
const mileageDiff = const mileageDiff =
contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength; contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength;
if (mileageDiff > 0) { if (mileageDiff > 0) {
@@ -139,7 +139,8 @@ export function ContractConvertToRo({
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100, federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate / 100,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100, state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate / 100,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100, local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate / 100,
ins_co_nm: "CC", ins_co_nm: values.ins_co_nm,
class: values.class,
converted: true, converted: true,
clm_no: contract.job.clm_no ? `${contract.job.clm_no}-CC` : null, clm_no: contract.job.clm_no ? `${contract.job.clm_no}-CC` : null,
ownr_fn: contract.job.owner.ownr_fn, ownr_fn: contract.job.owner.ownr_fn,
@@ -158,6 +159,7 @@ export function ContractConvertToRo({
text: t("contracts.labels.noteconvertedfrom", { text: t("contracts.labels.noteconvertedfrom", {
agreementnumber: contract.agreementnumber, agreementnumber: contract.agreementnumber,
}), }),
audit: true,
created_by: currentUser.email, created_by: currentUser.email,
}, },
], ],
@@ -306,6 +308,42 @@ export function ContractConvertToRo({
const popContent = ( const popContent = (
<div> <div>
<Form onFinish={handleFinish}> <Form onFinish={handleFinish}>
<Form.Item
name={["ins_co_nm"]}
label={t("jobs.fields.ins_co_nm")}
rules={[
{
required: true,
message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s.name} value={s.name}>
{s.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
name={"class"}
label={t("jobs.fields.class")}
rules={[
{
required: bodyshop.enforce_class,
message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_classes.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item <Form.Item
label={t("contracts.labels.convertform.applycleanupcharge")} label={t("contracts.labels.convertform.applycleanupcharge")}
rules={[ rules={[

View File

@@ -1,6 +1,7 @@
import { Form, Input, InputNumber } from "antd"; import { Form, Input, InputNumber, Space } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
import ContractStatusSelector from "../contract-status-select/contract-status-select.component"; import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
@@ -19,9 +20,7 @@ export default function ContractFormComponent({
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <div>
<div className="imex-flex-row__grow imex-flex-row__margin-large"> <FormFieldsChanged form={form} />
<FormFieldsChanged form={form} />
</div>
<LayoutFormRow> <LayoutFormRow>
{create ? null : ( {create ? null : (
<Form.Item <Form.Item
@@ -65,7 +64,6 @@ export default function ContractFormComponent({
</Form.Item> </Form.Item>
)} )}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow>
<Form.Item <Form.Item
label={t("contracts.fields.kmstart")} label={t("contracts.fields.kmstart")}
@@ -104,14 +102,17 @@ export default function ContractFormComponent({
</Form.Item> </Form.Item>
)} )}
</LayoutFormRow> </LayoutFormRow>
{selectedJobState && ( <Space wrap>
<div> {selectedJobState && (
<ContractFormJobPrefill <div>
jobId={selectedJobState && selectedJobState[0]} <ContractFormJobPrefill
form={form} jobId={selectedJobState && selectedJobState[0]}
/> form={form}
</div> />
)} </div>
)}
<ContractLicenseDecodeButton form={form} />
</Space>
<LayoutFormRow> <LayoutFormRow>
<Form.Item <Form.Item
label={t("contracts.fields.driver_dlnumber")} label={t("contracts.fields.driver_dlnumber")}
@@ -221,7 +222,6 @@ export default function ContractFormComponent({
<FormDatePicker /> <FormDatePicker />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("contracts.fields.dailyrate")} name="dailyrate"> <Form.Item label={t("contracts.fields.dailyrate")} name="dailyrate">
<InputNumber precision={2} /> <InputNumber precision={2} />

View File

@@ -1,7 +1,8 @@
import { Table, Input } from "antd"; import { Input, Table } from "antd";
import React, { useState } from "react"; import React, { useState, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
export default function ContractsJobsComponent({ export default function ContractsJobsComponent({
loading, loading,
@@ -158,6 +159,15 @@ export default function ContractsJobsComponent({
.includes(state.search.toLowerCase()) .includes(state.search.toLowerCase())
); );
const defaultCurrent = useMemo(() => {
return (
Math.round(
((filteredData.findIndex((v) => v.id === selectedJob) || 0) + 1) / 10
) + 1
);
}, [filteredData, selectedJob]);
if (loading) return <LoadingSkeleton />;
return ( return (
<Table <Table
loading={loading} loading={loading}
@@ -169,8 +179,11 @@ export default function ContractsJobsComponent({
/> />
)} )}
size="small" size="small"
pagination={{ position: "top" }} pagination={{
columns={columns.map((item) => ({ ...item }))} position: "top",
defaultCurrent: defaultCurrent,
}}
columns={columns}
rowKey="id" rowKey="id"
dataSource={filteredData} dataSource={filteredData}
onChange={handleTableChange} onChange={handleTableChange}
@@ -179,6 +192,13 @@ export default function ContractsJobsComponent({
type: "radio", type: "radio",
selectedRowKeys: [selectedJob], selectedRowKeys: [selectedJob],
}} }}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleSelect(record);
},
};
}}
/> />
); );
} }

View File

@@ -1,23 +1,16 @@
import { Slider } from "antd"; import { Slider } from "antd";
import React, { useEffect, useState, forwardRef } from "react"; import React, { forwardRef } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const CourtesyCarFuelComponent = ({ value = 100, onChange }, ref) => { const CourtesyCarFuelComponent = (props, ref) => {
const [option, setOption] = useState(value);
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
if (value !== option && onChange) {
onChange(option);
}
}, [value, option, onChange]);
const marks = { const marks = {
0: { 0: {
style: { style: {
color: "#f50", color: "#f50",
}, },
label: t("courtesycars.labels.fuel.empty"), label: <strong>{t("courtesycars.labels.fuel.empty")}</strong>,
}, },
13: t("courtesycars.labels.fuel.18"), 13: t("courtesycars.labels.fuel.18"),
25: t("courtesycars.labels.fuel.14"), 25: t("courtesycars.labels.fuel.14"),
@@ -34,14 +27,6 @@ const CourtesyCarFuelComponent = ({ value = 100, onChange }, ref) => {
}, },
}; };
return ( return <Slider ref={ref} marks={marks} step={null} {...props} />;
<Slider
ref={ref}
marks={marks}
defaultValue={value}
onChange={setOption}
step={null}
/>
);
}; };
export default forwardRef(CourtesyCarFuelComponent); export default forwardRef(CourtesyCarFuelComponent);

View File

@@ -13,7 +13,10 @@ export default function FormsFieldChanged({ form }) {
const loc = useLocation(); const loc = useLocation();
return ( return (
<Form.Item shouldUpdate style={{ margin: 0, padding: 0 }}> <Form.Item
shouldUpdate
//style={{ margin: 0, padding: 0 }}
>
{() => { {() => {
if (form.isFieldsTouched()) if (form.isFieldsTouched())
return ( return (

View File

@@ -1,5 +1,9 @@
import { List } from "antd"; import { List } from "antd";
import { WarningFilled, EyeInvisibleFilled } from "@ant-design/icons"; import {
WarningFilled,
EyeInvisibleFilled,
AuditOutlined,
} from "@ant-design/icons";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CardTemplate from "./job-detail-cards.template.component"; import CardTemplate from "./job-detail-cards.template.component";
@@ -31,6 +35,7 @@ export default function JobDetailCardsNotesComponent({ loading, data }) {
<EyeInvisibleFilled style={{ margin: 4, color: "red" }} /> <EyeInvisibleFilled style={{ margin: 4, color: "red" }} />
) : null} ) : null}
{item.private ? <WarningFilled style={{ margin: 4 }} /> : null} {item.private ? <WarningFilled style={{ margin: 4 }} /> : null}
{item.audit ? <AuditOutlined style={{ margin: 4 }} /> : null}
{item.text} {item.text}
</List.Item> </List.Item>
)} )}

View File

@@ -122,6 +122,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
notes: { notes: {
data: { data: {
created_by: currentUser.email, created_by: currentUser.email,
audit: true,
text: t("jobs.labels.importnote", { text: t("jobs.labels.importnote", {
date: moment().format("MM/DD/yyy"), date: moment().format("MM/DD/yyy"),
time: moment().format("hh:mm a"), time: moment().format("hh:mm a"),
@@ -279,6 +280,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
{ {
jobid: selectedJob, jobid: selectedJob,
created_by: currentUser.email, created_by: currentUser.email,
audit: true,
text: t("jobs.labels.supplementnote", { text: t("jobs.labels.supplementnote", {
date: moment().format("MM/DD/yyy"), date: moment().format("MM/DD/yyy"),
time: moment().format("hh:mm a"), time: moment().format("hh:mm a"),

View File

@@ -1,22 +1,28 @@
import { DownCircleFilled } from "@ant-design/icons"; import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client"; import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification, Popconfirm } from "antd"; import { Button, Dropdown, Menu, notification, Popconfirm } from "antd";
import moment from "moment";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component"; import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -35,6 +41,7 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsDetailHeaderActions({ export function JobsDetailHeaderActions({
job, job,
bodyshop, bodyshop,
currentUser,
refetch, refetch,
setScheduleContext, setScheduleContext,
setBillEnterContext, setBillEnterContext,
@@ -48,6 +55,7 @@ export function JobsDetailHeaderActions({
const history = useHistory(); const history = useHistory();
const [deleteJob] = useMutation(DELETE_JOB); const [deleteJob] = useMutation(DELETE_JOB);
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const [voidJob] = useMutation(VOID_JOB);
const jobInProduction = useMemo(() => { const jobInProduction = useMemo(() => {
return bodyshop.md_ro_statuses.production_statuses.includes(job.status); return bodyshop.md_ro_statuses.production_statuses.includes(job.status);
}, [job, bodyshop.md_ro_statuses.production_statuses]); }, [job, bodyshop.md_ro_statuses.production_statuses]);
@@ -338,13 +346,24 @@ export function JobsDetailHeaderActions({
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
onConfirm={async () => { onConfirm={async () => {
//delete the job. //delete the job.
const result = await updateJob({ const result = await voidJob({
variables: { variables: {
jobId: job.id, jobId: job.id,
job: { job: {
status: bodyshop.md_ro_statuses.default_void, status: bodyshop.md_ro_statuses.default_void,
voided: true, voided: true,
}, },
note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote", {
date: moment().format("MM/DD/yyy"),
time: moment().format("hh:mm a"),
}),
},
],
}, },
}); });

View File

@@ -1,4 +1,5 @@
import { import {
AuditOutlined,
DeleteFilled, DeleteFilled,
EditFilled, EditFilled,
EyeInvisibleFilled, EyeInvisibleFilled,
@@ -8,16 +9,23 @@ import { Button, Table } 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 { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setNoteUpsertContext: (context) => setNoteUpsertContext: (context) =>
dispatch(setModalContext({ context: context, modal: "noteUpsert" })), dispatch(setModalContext({ context: context, modal: "noteUpsert" })),
}); });
export function JobNotesComponent({ export function JobNotesComponent({
jobRO,
loading, loading,
data, data,
refetch, refetch,
@@ -40,6 +48,7 @@ export function JobNotesComponent({
<WarningFilled style={{ margin: 4, color: "red" }} /> <WarningFilled style={{ margin: 4, color: "red" }} />
) : null} ) : null}
{record.private ? <EyeInvisibleFilled style={{ margin: 4 }} /> : null} {record.private ? <EyeInvisibleFilled style={{ margin: 4 }} /> : null}
{record.audit ? <AuditOutlined style={{ margin: 4 }} /> : null}
</span> </span>
), ),
}, },
@@ -76,11 +85,13 @@ export function JobNotesComponent({
<span> <span>
<Button <Button
loading={deleteLoading} loading={deleteLoading}
disabled={record.audit || jobRO}
onClick={() => handleNoteDelete(record.id)} onClick={() => handleNoteDelete(record.id)}
> >
<DeleteFilled /> <DeleteFilled />
</Button> </Button>
<Button <Button
disabled={record.audit || jobRO}
onClick={() => { onClick={() => {
setNoteUpsertContext({ setNoteUpsertContext({
actions: { refetch: refetch }, actions: { refetch: refetch },
@@ -123,4 +134,4 @@ export function JobNotesComponent({
</div> </div>
); );
} }
export default connect(null, mapDispatchToProps)(JobNotesComponent); export default connect(mapStateToProps, mapDispatchToProps)(JobNotesComponent);

View File

@@ -9,6 +9,7 @@ export default function ScheduleDayViewComponent({ data, day }) {
<ScheduleCalendarWrapperComponent <ScheduleCalendarWrapperComponent
events={data} events={data}
defaultView="day" defaultView="day"
view={"day"}
views={["day"]} views={["day"]}
date={day} date={day}
/> />

View File

@@ -15,7 +15,7 @@ export default function ScheduleDayViewContainer({ day }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
}); });
const { t } = useTranslation(); const { t } = useTranslation();
if (!!!day) return <div>{t("appointments.labels.nodateselected")}</div>; if (!day) return <div>{t("appointments.labels.nodateselected")}</div>;
if (loading) return <LoadingSkeleton paragraph={{ rows: 4 }} />; if (loading) return <LoadingSkeleton paragraph={{ rows: 4 }} />;
if (error) return <div>{error.message}</div>; if (error) return <div>{error.message}</div>;
let normalizedData; let normalizedData;

View File

@@ -756,6 +756,27 @@ export const UPDATE_JOB = gql`
} }
`; `;
export const VOID_JOB = gql`
mutation VOID_JOB(
$jobId: uuid!
$job: jobs_set_input!
$note: [notes_insert_input!]!
) {
update_jobs_by_pk(_set: $job, pk_columns: { id: $jobId }) {
id
date_exported
status
alt_transport
ro_number
production_vars
lbr_adjustments
}
insert_notes(objects: $note) {
affected_rows
}
}
`;
export const UPDATE_JOBS = gql` export const UPDATE_JOBS = gql`
mutation UPDATE_JOBS($jobIds: [uuid!]!, $fields: jobs_set_input!) { mutation UPDATE_JOBS($jobIds: [uuid!]!, $fields: jobs_set_input!) {
update_jobs(where: { id: { _in: $jobIds } }, _set: $fields) { update_jobs(where: { id: { _in: $jobIds } }, _set: $fields) {

View File

@@ -23,6 +23,7 @@ export const QUERY_NOTES_BY_JOB_PK = gql`
private private
text text
updated_at updated_at
audit
} }
} }
} }

View File

@@ -20,8 +20,8 @@ import "./utils/CleanAxios";
import "antd/dist/antd.less"; import "antd/dist/antd.less";
require("dotenv").config(); require("dotenv").config();
Dinero.defaultCurrency = "CAD"; // Dinero.defaultCurrency = "CAD";
Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_UP"; Dinero.globalRoundingMode = "HALF_UP";
if (process.env.NODE_ENV !== "development") { if (process.env.NODE_ENV !== "development") {

View File

@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import ContractCarsContainer from "../../components/contract-cars/contract-cars.container"; import ContractCarsContainer from "../../components/contract-cars/contract-cars.container";
import ContractFormComponent from "../../components/contract-form/contract-form.component"; import ContractFormComponent from "../../components/contract-form/contract-form.component";
import ContractJobsContainer from "../../components/contract-jobs/contract-jobs.container"; import ContractJobsContainer from "../../components/contract-jobs/contract-jobs.container";
import ContractLicenseDecodeButton from "../../components/contract-license-decode-button/contract-license-decode-button.component";
export default function ContractCreatePageComponent({ export default function ContractCreatePageComponent({
form, form,
selectedJobState, selectedJobState,
@@ -14,7 +13,12 @@ export default function ContractCreatePageComponent({
const { t } = useTranslation(); const { t } = useTranslation();
const CreateButton = ( const CreateButton = (
<Button type="primary" onClick={() => form.submit()} loading={loading}> <Button
disabled={!selectedJobState[0] || !selectedCarState[0]}
type="primary"
onClick={() => form.submit()}
loading={loading}
>
{t("general.actions.create")} {t("general.actions.create")}
</Button> </Button>
); );
@@ -24,12 +28,18 @@ export default function ContractCreatePageComponent({
{CreateButton} {CreateButton}
<ContractJobsContainer selectedJobState={selectedJobState} /> <ContractJobsContainer selectedJobState={selectedJobState} />
<ContractCarsContainer selectedCarState={selectedCarState} form={form} /> <ContractCarsContainer selectedCarState={selectedCarState} form={form} />
<ContractLicenseDecodeButton form={form} />
<ContractFormComponent <div
create style={{
form={form} display: selectedJobState[0] && selectedCarState[0] ? "" : "none",
selectedJobState={selectedJobState} }}
/> >
<ContractFormComponent
create
form={form}
selectedJobState={selectedJobState}
/>
</div>
{CreateButton} {CreateButton}
</div> </div>
); );

View File

@@ -100,7 +100,6 @@ export function ContractCreatePageContainer({
layout="vertical" layout="vertical"
autoComplete="no" autoComplete="no"
onFinish={handleFinish} onFinish={handleFinish}
initialValues={{ fuelout: 100 }}
> >
<ContractCreatePageComponent <ContractCreatePageComponent
loading={loading} loading={loading}

View File

@@ -1238,7 +1238,8 @@
"vehicle_info": "Vehicle", "vehicle_info": "Vehicle",
"vehicleassociation": "Vehicle Association", "vehicleassociation": "Vehicle Association",
"viewallocations": "View Allocations", "viewallocations": "View Allocations",
"voidjob": "Are you sure you want to void this job? This cannot be easily undone. " "voidjob": "Are you sure you want to void this job? This cannot be easily undone. ",
"voidnote": "This repair order was voided on {{date}} at {{time}}."
}, },
"successes": { "successes": {
"addedtoproduction": "Job added to production board.", "addedtoproduction": "Job added to production board.",

View File

@@ -1238,7 +1238,8 @@
"vehicle_info": "Vehículo", "vehicle_info": "Vehículo",
"vehicleassociation": "", "vehicleassociation": "",
"viewallocations": "", "viewallocations": "",
"voidjob": "" "voidjob": "",
"voidnote": ""
}, },
"successes": { "successes": {
"addedtoproduction": "", "addedtoproduction": "",

View File

@@ -1238,7 +1238,8 @@
"vehicle_info": "Véhicule", "vehicle_info": "Véhicule",
"vehicleassociation": "", "vehicleassociation": "",
"viewallocations": "", "viewallocations": "",
"voidjob": "" "voidjob": "",
"voidnote": ""
}, },
"successes": { "successes": {
"addedtoproduction": "", "addedtoproduction": "",

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."notes" DROP COLUMN "audit";
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."notes" ADD COLUMN "audit" boolean NOT NULL DEFAULT
false;
type: run_sql

View File

@@ -0,0 +1,33 @@
- args:
role: user
table:
name: notes
schema: public
type: drop_insert_permission
- args:
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- jobid
- text
- created_by
- critical
- private
set: {}
role: user
table:
name: notes
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,34 @@
- args:
role: user
table:
name: notes
schema: public
type: drop_insert_permission
- args:
permission:
check:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- audit
- created_at
- created_by
- critical
- id
- jobid
- private
- text
- updated_at
set: {}
role: user
table:
name: notes
schema: public
type: create_insert_permission

View File

@@ -0,0 +1,34 @@
- args:
role: user
table:
name: notes
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- critical
- private
- created_by
- text
- created_at
- updated_at
- id
- jobid
computed_fields: []
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: notes
schema: public
type: create_select_permission

View File

@@ -0,0 +1,35 @@
- args:
role: user
table:
name: notes
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- audit
- created_at
- created_by
- critical
- id
- jobid
- private
- text
- updated_at
computed_fields: []
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: notes
schema: public
type: create_select_permission

View File

@@ -0,0 +1,33 @@
- args:
role: user
table:
name: notes
schema: public
type: drop_update_permission
- args:
permission:
columns:
- critical
- private
- created_by
- text
- created_at
- updated_at
- id
- jobid
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: notes
schema: public
type: create_update_permission

View File

@@ -0,0 +1,34 @@
- args:
role: user
table:
name: notes
schema: public
type: drop_update_permission
- args:
permission:
columns:
- audit
- created_at
- created_by
- critical
- id
- jobid
- private
- text
- updated_at
filter:
job:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: notes
schema: public
type: create_update_permission

View File

@@ -3129,26 +3129,28 @@ tables:
- active: - active:
_eq: true _eq: true
columns: columns:
- id - audit
- created_at - created_at
- updated_at
- jobid
- text
- created_by - created_by
- critical - critical
- id
- jobid
- private - private
- text
- updated_at
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- critical - audit
- private
- created_by
- text
- created_at - created_at
- updated_at - created_by
- critical
- id - id
- jobid - jobid
- private
- text
- updated_at
filter: filter:
job: job:
bodyshop: bodyshop:
@@ -3163,14 +3165,15 @@ tables:
- role: user - role: user
permission: permission:
columns: columns:
- critical - audit
- private
- created_by
- text
- created_at - created_at
- updated_at - created_by
- critical
- id - id
- jobid - jobid
- private
- text
- updated_at
filter: filter:
job: job:
bodyshop: bodyshop:

8190
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,6 @@
"firebase-admin": "^9.5.0", "firebase-admin": "^9.5.0",
"graphql": "^15.5.0", "graphql": "^15.5.0",
"graphql-request": "^3.4.0", "graphql-request": "^3.4.0",
"handlebars": "^4.7.7",
"inline-css": "^3.0.0", "inline-css": "^3.0.0",
"intuit-oauth": "^3.0.2", "intuit-oauth": "^3.0.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@@ -2,8 +2,8 @@ const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const GraphQLClient = require("graphql-request").GraphQLClient; const GraphQLClient = require("graphql-request").GraphQLClient;
Dinero.defaultCurrency = "USD"; // Dinero.defaultCurrency = "USD";
Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_UP"; Dinero.globalRoundingMode = "HALF_UP";
exports.totalsSsu = async function (req, res) { exports.totalsSsu = async function (req, res) {
@@ -173,9 +173,9 @@ function CalculateRatesTotals(ratesList, shoprates) {
let subtotal = Dinero({ amount: 0 }); let subtotal = Dinero({ amount: 0 });
let rates_subtotal = Dinero({ amount: 0 }); let rates_subtotal = Dinero({ amount: 0 });
for (const property in ret) { for (const property in ret) {
ret[property].total = Dinero({ amount: ret[property].rate * 100 }).multiply( ret[property].total = Dinero({
ret[property].hours amount: Math.round((ret[property].rate || 0) * 100),
); }).multiply(ret[property].hours);
subtotal = subtotal.add(ret[property].total); subtotal = subtotal.add(ret[property].total);
if (property !== "mapa" && property !== "mash") if (property !== "mapa" && property !== "mash")
rates_subtotal = rates_subtotal.add(ret[property].total); rates_subtotal = rates_subtotal.add(ret[property].total);

View File

@@ -1,213 +1,212 @@
const path = require("path"); // const path = require("path");
const moment = require("moment"); // const moment = require("moment");
require("dotenv").config({ // require("dotenv").config({
path: path.resolve( // path: path.resolve(
process.cwd(), // process.cwd(),
`.env.${process.env.NODE_ENV || "development"}` // `.env.${process.env.NODE_ENV || "development"}`
), // ),
}); // });
var _ = require("lodash"); // var _ = require("lodash");
const Handlebars = require("handlebars"); // const Handlebars = require("handlebars");
const phone = require("phone"); // const phone = require("phone");
var Dinero = require("dinero.js"); // var Dinero = require("dinero.js");
Dinero.defaultCurrency = "CAD"; // Dinero.defaultCurrency = "CAD";
Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
//Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}} // //Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}}
Handlebars.registerHelper("round", function (context, block) { // Handlebars.registerHelper("round", function (context, block) {
if (context && context.hash) { // if (context && context.hash) {
block = _.cloneDeep(context); // block = _.cloneDeep(context);
context = undefined; // context = undefined;
} // }
try { // try {
return context.toFixed(2); // return context.toFixed(2);
} catch { // } catch {
return context; // return context;
} // }
}); // });
Handlebars.registerHelper("dinerof", function (context, block) { // Handlebars.registerHelper("dinerof", function (context, block) {
if (context && context.hash) { // if (context && context.hash) {
block = _.cloneDeep(context); // block = _.cloneDeep(context);
context = undefined; // context = undefined;
} // }
var amount = Dinero(context); // var amount = Dinero(context);
if (context) { // if (context) {
return amount.toFormat(); // return amount.toFormat();
} // }
return ""; // return "";
}); // });
Handlebars.registerHelper("phonef", function (context, block) { // Handlebars.registerHelper("phonef", function (context, block) {
if (context && context.hash) { // if (context && context.hash) {
block = _.cloneDeep(context); // block = _.cloneDeep(context);
context = undefined; // context = undefined;
} // }
var ph = phone(context)[0]; // var ph = phone(context)[0];
if (context) { // if (context) {
return ph; // return ph;
} // }
return ""; // return "";
}); // });
Handlebars.registerHelper("partType", function (context, block) { // Handlebars.registerHelper("partType", function (context, block) {
if (!context) return ""; // if (!context) return "";
switch (context.toUpperCase()) { // switch (context.toUpperCase()) {
case "PAA": // case "PAA":
return "Aftermarket"; // return "Aftermarket";
case "PAE": // case "PAE":
return "Existing"; // return "Existing";
case "PAN": // case "PAN":
return "OEM"; // return "OEM";
case "PAO": // case "PAO":
return "Other"; // return "Other";
case "PAS": // case "PAS":
return "Sublet"; // return "Sublet";
case "PASL": // case "PASL":
return "Sublet"; // return "Sublet";
case "PAL": // case "PAL":
return "LKQ"; // return "LKQ";
case "PAM": // case "PAM":
return "Remanufactured"; // return "Remanufactured";
case "PAC": // case "PAC":
return "Chrome"; // return "Chrome";
case "PAP": // case "PAP":
return "OEM Partial"; // return "OEM Partial";
case "PAR": // case "PAR":
return "Record"; // return "Record";
default: // default:
return context; // return context;
} // }
}); // });
Handlebars.registerHelper("lbrType", function (context, block) { // Handlebars.registerHelper("lbrType", function (context, block) {
if (!context) return ""; // if (!context) return "";
switch (context.toUpperCase()) { // switch (context.toUpperCase()) {
case "LAA": // case "LAA":
return "Aluminum"; // return "Aluminum";
case "LAB": // case "LAB":
return "Body"; // return "Body";
case "LAD": // case "LAD":
return "Diagnostic"; // return "Diagnostic";
case "LAF": // case "LAF":
return "Frame"; // return "Frame";
case "LAG": // case "LAG":
return "Glass"; // return "Glass";
case "LAM": // case "LAM":
return "Mechanical"; // return "Mechanical";
case "LAR": // case "LAR":
return "Refinish"; // return "Refinish";
case "LAS": // case "LAS":
return "Structural"; // return "Structural";
case "LAU": // case "LAU":
return "Detail"; // return "Detail";
default: // default:
return context; // return context;
} // }
}); // });
Handlebars.registerHelper("objectKeys", function (obj, block) { // Handlebars.registerHelper("objectKeys", function (obj, block) {
var accum = ""; // var accum = "";
obj && // obj &&
Object.keys(obj).map((key) => { // Object.keys(obj).map((key) => {
accum += block.fn({ key, value: obj[key] }); // accum += block.fn({ key, value: obj[key] });
}); // });
return accum; // return accum;
}); // });
Handlebars.registerHelper("dinero", function (context, block) { // Handlebars.registerHelper("dinero", function (context, block) {
if (context && context.hash) { // if (context && context.hash) {
block = _.cloneDeep(context); // block = _.cloneDeep(context);
context = undefined; // context = undefined;
} // }
var amount = Dinero({ // var amount = Dinero({
amount: Math.round((context || 0) * 100), // amount: Math.round((context || 0) * 100),
currency: "CAD", // currency: "CAD",
}); // });
return amount.toFormat(); // return amount.toFormat();
}); // });
Handlebars.registerHelper("moment", function (context, block) { // Handlebars.registerHelper("moment", function (context, block) {
if (context && context.hash) { // if (context && context.hash) {
block = _.cloneDeep(context); // block = _.cloneDeep(context);
context = undefined; // context = undefined;
} // }
if (!!!context) return ""; // if (!!!context) return "";
var date = moment(context); // var date = moment(context);
if (block.hash.timezone) { // if (block.hash.timezone) {
date.tz(block.hash.timezone); // date.tz(block.hash.timezone);
} // }
var hasFormat = false; // var hasFormat = false;
// Reset the language back to default before doing anything else // // Reset the language back to default before doing anything else
date.locale("en"); // date.locale("en");
for (var i in block.hash) { // for (var i in block.hash) {
if (i === "format") { // if (i === "format") {
hasFormat = true; // hasFormat = true;
} else if (date[i]) { // } else if (date[i]) {
date = date[i](block.hash[i]); // date = date[i](block.hash[i]);
} else { // } else {
console.log('moment.js does not support "' + i + '"'); // console.log('moment.js does not support "' + i + '"');
} // }
} // }
if (hasFormat) { // if (hasFormat) {
date = date.format(block.hash.format); // date = date.format(block.hash.format);
} // }
return date; // return date;
}); // });
Handlebars.registerHelper("duration", function (context, block) { // Handlebars.registerHelper("duration", function (context, block) {
if (context && context.hash) { // if (context && context.hash) {
block = _.cloneDeep(context); // block = _.cloneDeep(context);
context = 0; // context = 0;
} // }
var duration = moment.duration(context); // var duration = moment.duration(context);
var hasFormat = false; // var hasFormat = false;
// Reset the language back to default before doing anything else // // Reset the language back to default before doing anything else
duration = duration.lang("en"); // duration = duration.lang("en");
for (var i in block.hash) { // for (var i in block.hash) {
if (i === "format") { // if (i === "format") {
hasFormat = true; // hasFormat = true;
} else if (duration[i]) { // } else if (duration[i]) {
duration = duration[i](block.hash[i]); // duration = duration[i](block.hash[i]);
} else { // } else {
console.log('moment.js duration does not support "' + i + '"'); // console.log('moment.js duration does not support "' + i + '"');
} // }
} // }
if (hasFormat) { // if (hasFormat) {
duration = duration.format(block.hash.format); // duration = duration.format(block.hash.format);
} // }
return duration; // return duration;
}); // });
exports.render = (req, res) => { exports.render = (req, res) => {
//Perform request validation // //Perform request validation
let view; // let view;
console.log("[HJS Render] New Render Request."); // console.log("[HJS Render] New Render Request.");
// //console.log("[HJS Render] Context", req.body.context);
//console.log("[HJS Render] Context", req.body.context); // if (req.body.context.bodyshop.template_header) {
if (req.body.context.bodyshop.template_header) { // console.log("[HJS Render] Including Header");
console.log("[HJS Render] Including Header"); // //view = req.body.view;
//view = req.body.view; // view = `${req.body.context.bodyshop.template_header}${req.body.view}`;
view = `${req.body.context.bodyshop.template_header}${req.body.view}`; // } else {
} else { // console.log("[HJS Render] No header to include.");
console.log("[HJS Render] No header to include."); // view = req.body.view;
view = req.body.view; // }
} // var template = Handlebars.compile(view);
var template = Handlebars.compile(view); // res.send(template(req.body.context));
res.send(template(req.body.context));
}; };