Merge branch 'test' into feature/qbo

This commit is contained in:
Patrick Fic
2021-09-16 17:04:57 -07:00
26 changed files with 19061 additions and 610 deletions

File diff suppressed because it is too large Load Diff

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
@@ -3683,6 +3683,69 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>itc_federal</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>
<name>itc_local</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>
<name>itc_state</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>mappingname</name> <name>mappingname</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -8,7 +8,25 @@ import { alphaSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component"; import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component"; import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
export default function AccountingReceivablesTableComponent({ loading, jobs }) { import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(AccountingReceivablesTableComponent);
export function AccountingReceivablesTableComponent({
bodyshop,
loading,
jobs,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]); const [selectedJobs, setSelectedJobs] = useState([]);
const [transInProgress, setTransInProgress] = useState(false); const [transInProgress, setTransInProgress] = useState(false);
@@ -180,12 +198,14 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
<Card <Card
extra={ extra={
<Space wrap> <Space wrap>
{!bodyshop.cdk_dealerid && (
<JobsExportAllButton <JobsExportAllButton
jobIds={selectedJobs} jobIds={selectedJobs}
disabled={transInProgress || selectedJobs.length === 0} disabled={transInProgress || selectedJobs.length === 0}
loadingCallback={setTransInProgress} loadingCallback={setTransInProgress}
completedCallback={setSelectedJobs} completedCallback={setSelectedJobs}
/> />
)}
<Input.Search <Input.Search
value={state.search} value={state.search}
onChange={handleSearch} onChange={handleSearch}

View File

@@ -1,4 +1,4 @@
import { Button, Table, Typography } from "antd"; import { Button, Card, Table, Typography } from "antd";
import React, { useState, useEffect } from "react"; import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -21,15 +21,16 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(DmsAllocationsSummary); )(DmsAllocationsSummary);
export function DmsAllocationsSummary({ socket, bodyshop, jobId }) { export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]); const [allocationsSummary, setAllocationsSummary] = useState([]);
useEffect(() => { useEffect(() => {
if (socket.connected) { if (socket.connected) {
socket.emit("cdk-calculate-allocations", jobId, (ack) => socket.emit("cdk-calculate-allocations", jobId, (ack) => {
setAllocationsSummary(ack) setAllocationsSummary(ack);
); socket.allocationsSummary = ack;
});
} }
}, [socket, socket.connected, jobId]); }, [socket, socket.connected, jobId]);
@@ -75,8 +76,9 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
]; ];
return ( return (
<Table <Card
title={() => ( title={title}
extra={
<Button <Button
onClick={() => { onClick={() => {
socket.emit("cdk-calculate-allocations", jobId, (ack) => socket.emit("cdk-calculate-allocations", jobId, (ack) =>
@@ -86,7 +88,9 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
> >
<SyncOutlined /> <SyncOutlined />
</Button> </Button>
)} }
>
<Table
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns} columns={columns}
rowKey="center" rowKey="center"
@@ -116,7 +120,9 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
{totals.totalSale.toFormat()} {totals.totalSale.toFormat()}
</Table.Summary.Cell> </Table.Summary.Cell>
<Table.Summary.Cell> <Table.Summary.Cell>
{totals.totalCost.toFormat()} {
// totals.totalCost.toFormat()
}
</Table.Summary.Cell> </Table.Summary.Cell>
<Table.Summary.Cell></Table.Summary.Cell> <Table.Summary.Cell></Table.Summary.Cell>
<Table.Summary.Cell></Table.Summary.Cell> <Table.Summary.Cell></Table.Summary.Cell>
@@ -124,5 +130,6 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
); );
}} }}
/> />
</Card>
); );
} }

View File

@@ -1,11 +1,11 @@
import { useLazyQuery } from "@apollo/client";
import { Button, Input, Modal, Table } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { Modal, Button, Table, Input } from "antd"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import { useLazyQuery } from "@apollo/client";
import { SEARCH_DMS_VEHICLES } from "../../graphql/dms.queries"; import { SEARCH_DMS_VEHICLES } from "../../graphql/dms.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -26,31 +26,27 @@ export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
useLazyQuery(SEARCH_DMS_VEHICLES); useLazyQuery(SEARCH_DMS_VEHICLES);
const columns = [ const columns = [
{ {
title: t("jobs.fields.dms.make"), title: t("vehicles.fields.v_make_desc"),
dataIndex: "make", dataIndex: "make",
key: "make", key: "make",
}, },
{ {
title: t("jobs.fields.dms.model"), title: t("vehicles.fields.v_model_desc"),
dataIndex: "model", dataIndex: "model",
key: "model", key: "model",
}, },
{ {
title: t("jobs.fields.dms.makecode"), title: t("jobs.fields.dms.dms_make"),
dataIndex: "makecode", dataIndex: "makecode",
key: "makecode", key: "makecode",
}, },
{ {
title: t("jobs.fields.dms.modelcode"), title: t("jobs.fields.dms.dms_model"),
dataIndex: "modelcode", dataIndex: "modelcode",
key: "modelcode", key: "modelcode",
}, },
]; ];
console.log(
"🚀 ~ file: dms-cdk-makes.component.jsx ~ line 95 ~ selectedModel",
selectedModel
);
return ( return (
<div> <div>
<Modal <Modal
@@ -102,7 +98,7 @@ export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
}); });
}} }}
> >
{t("jobs.actions.dms.getmakes")} {t("jobs.actions.dms.findmakemodelcode")}
</Button> </Button>
</div> </div>
); );

View File

@@ -4,6 +4,7 @@ import React, { useState } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -15,6 +16,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakesRefetch);
export function DmsCdkMakesRefetch({ bodyshop, form, socket }) { export function DmsCdkMakesRefetch({ bodyshop, form, socket }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const handleRefetch = async () => { const handleRefetch = async () => {
setLoading(true); setLoading(true);
const response = await axios.post("/cdk/getvehicles", { const response = await axios.post("/cdk/getvehicles", {
@@ -26,7 +28,7 @@ export function DmsCdkMakesRefetch({ bodyshop, form, socket }) {
}; };
return ( return (
<Button loading={loading} onClick={handleRefetch}> <Button loading={loading} onClick={handleRefetch}>
Refetch Models {t("jobs.actions.dms.refetchmakesmodels")}
</Button> </Button>
); );
} }

View File

@@ -1,4 +1,4 @@
import { Button, Table } from "antd"; import { Button, Table, Col , Checkbox} from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -32,6 +32,7 @@ export function DmsCustomerSelector({ bodyshop }) {
const onUseSelected = () => { const onUseSelected = () => {
setVisible(false); setVisible(false);
socket.emit("cdk-selected-customer", selectedCustomer); socket.emit("cdk-selected-customer", selectedCustomer);
setSelectedCustomer(null);
}; };
const onUseGeneric = () => { const onUseGeneric = () => {
@@ -40,23 +41,36 @@ export function DmsCustomerSelector({ bodyshop }) {
"cdk-selected-customer", "cdk-selected-customer",
bodyshop.cdk_configuration.generic_customer_number bodyshop.cdk_configuration.generic_customer_number
); );
setSelectedCustomer(null);
}; };
const onCreateNew = () => { const onCreateNew = () => {
setVisible(false); setVisible(false);
socket.emit("cdk-selected-customer", null); socket.emit("cdk-selected-customer", null);
setSelectedCustomer(null);
}; };
const columns = [ const columns = [
{ {
title: t("dms.fields.name1"), title: t("jobs.fields.dms.id"),
dataIndex: ["id", "value"],
key: "id",
},
{
title: t("jobs.fields.dms.vinowner"),
dataIndex: "vinOwner",
key: "vinOwner",
render: (text, record) => <Checkbox disabled checked={record.vinOwner}/>
},
{
title: t("jobs.fields.dms.name1"),
dataIndex: ["name1", "fullName"], dataIndex: ["name1", "fullName"],
key: "name1", key: "name1",
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName), sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
}, },
{ {
title: t("dms.fields.address"), title: t("jobs.fields.dms.address"),
//dataIndex: ["name2", "fullName"], //dataIndex: ["name2", "fullName"],
key: "address", key: "address",
render: (record, value) => render: (record, value) =>
@@ -66,6 +80,7 @@ export function DmsCustomerSelector({ bodyshop }) {
if (!visible) return <></>; if (!visible) return <></>;
return ( return (
<Col span={24}>
<Table <Table
title={() => ( title={() => (
<div> <div>
@@ -101,5 +116,6 @@ export function DmsCustomerSelector({ bodyshop }) {
selectedRowKeys: [selectedCustomer], selectedRowKeys: [selectedCustomer],
}} }}
/> />
</Col>
); );
} }

View File

@@ -1,24 +1,26 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled } from "@ant-design/icons";
import { import {
Button, Button,
Card,
Divider,
Form, Form,
Input, Input,
InputNumber, InputNumber,
Select, Select,
Space, Space,
Statistic, Statistic,
Typography,
} from "antd"; } from "antd";
import Dinero from "dinero.js";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { determineDmsType } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component"; import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import Dinero from "dinero.js";
import { determineDmsType } from "../../pages/dms/dms.container";
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -58,8 +60,19 @@ export function DmsPostForm({ bodyshop, socket, job }) {
}; };
return ( return (
<Form form={form} layout="vertical" onFinish={handleFinish}> <Card title={t("jobs.labels.dms.postingform")}>
<LayoutFormRow> <Form
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{
story: t("jobs.labels.dms.defaultstory", {
ro_number: job.ro_number,
area_of_damage: job.area_of_damage && job.area_of_damage.impact1,
}).substr(0, 239),
}}
>
<LayoutFormRow grow>
<Form.Item <Form.Item
name="journal" name="journal"
label={t("jobs.fields.dms.journal")} label={t("jobs.fields.dms.journal")}
@@ -76,17 +89,6 @@ export function DmsPostForm({ bodyshop, socket, job }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
name="story"
label={t("jobs.fields.dms.story")}
rules={[
{
required: true,
},
]}
>
<Input.TextArea />
</Form.Item>
<Form.Item <Form.Item
name="kmin" name="kmin"
label={t("jobs.fields.kmin")} label={t("jobs.fields.kmin")}
@@ -113,6 +115,9 @@ export function DmsPostForm({ bodyshop, socket, job }) {
> >
<InputNumber disabled /> <InputNumber disabled />
</Form.Item> </Form.Item>
</LayoutFormRow>
<LayoutFormRow style={{ justifyContent: "center" }} grow>
<Form.Item <Form.Item
name="dms_make" name="dms_make"
label={t("jobs.fields.dms.dms_make")} label={t("jobs.fields.dms.dms_make")}
@@ -135,17 +140,29 @@ export function DmsPostForm({ bodyshop, socket, job }) {
> >
<Input disabled /> <Input disabled />
</Form.Item> </Form.Item>
<DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakesRefetch />
</LayoutFormRow>
<DmsCdkMakes form={form} socket={socket} job={job} />
{/* <DmsCdkMakesRefetch /> */}
</LayoutFormRow>
<Form.Item
name="story"
label={t("jobs.fields.dms.story")}
rules={[
{
required: true,
},
]}
>
<Input.TextArea maxLength={240} />
</Form.Item>
<Divider />
<Form.List name={["payers"]}> <Form.List name={["payers"]}>
{(fields, { add, remove }) => { {(fields, { add, remove }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item key={field.key}> <Form.Item key={field.key}>
<LayoutFormRow> <Space wrap>
<Form.Item <Form.Item
label={t("jobs.fields.dms.payer.name")} label={t("jobs.fields.dms.payer.name")}
key={`${index}name`} key={`${index}name`}
@@ -157,6 +174,7 @@ export function DmsPostForm({ bodyshop, socket, job }) {
]} ]}
> >
<Select <Select
style={{ minWidth: "15rem" }}
onSelect={(value) => handlePayerSelect(value, index)} onSelect={(value) => handlePayerSelect(value, index)}
> >
{bodyshop.cdk_configuration && {bodyshop.cdk_configuration &&
@@ -234,7 +252,7 @@ export function DmsPostForm({ bodyshop, socket, job }) {
remove(field.name); remove(field.name);
}} }}
/> />
</LayoutFormRow> </Space>
</Form.Item> </Form.Item>
))} ))}
<Form.Item> <Form.Item>
@@ -246,7 +264,7 @@ export function DmsPostForm({ bodyshop, socket, job }) {
}} }}
style={{ width: "100%" }} style={{ width: "100%" }}
> >
{t("dms.actions.addpayer")} {t("jobs.actions.dms.addpayer")}
</Button> </Button>
</Form.Item> </Form.Item>
</div> </div>
@@ -265,19 +283,36 @@ export function DmsPostForm({ bodyshop, socket, job }) {
Dinero({ amount: Math.round((payer?.amount || 0) * 100) }) Dinero({ amount: Math.round((payer?.amount || 0) * 100) })
); );
}); });
const discrep = Dinero(job.job_totals.totals.total_repairs).subtract(
totalAllocated const totals =
socket.allocationsSummary &&
socket.allocationsSummary.reduce(
(acc, val) => {
return {
totalSale: acc.totalSale.add(Dinero(val.sale)),
totalCost: acc.totalCost.add(Dinero(val.cost)),
};
},
{
totalSale: Dinero(),
totalCost: Dinero(),
}
); );
const discrep = totals
? totals.totalSale.subtract(totalAllocated)
: Dinero();
return ( return (
<Space> <Space size="large" wrap align="center">
<Statistic
title={t("jobs.labels.subtotal")}
value={(totals ? totals.totalSale : Dinero()).toFormat()}
/>
<Typography.Title>-</Typography.Title>
<Statistic <Statistic
title={t("jobs.labels.dms.totalallocated")} title={t("jobs.labels.dms.totalallocated")}
value={totalAllocated.toFormat()} value={totalAllocated.toFormat()}
/> />
<Statistic <Typography.Title>=</Typography.Title>
title={t("jobs.fields.subtotal")}
value={Dinero(job.job_totals.totals.total_repairs).toFormat()}
/>
<Statistic <Statistic
title={t("jobs.labels.dms.notallocated")} title={t("jobs.labels.dms.notallocated")}
valueStyle={{ valueStyle={{
@@ -285,24 +320,19 @@ export function DmsPostForm({ bodyshop, socket, job }) {
}} }}
value={discrep.toFormat()} value={discrep.toFormat()}
/> />
<Button //disabled={discrep.getAmount() !== 0} //TODO: REMOVE THIS COMMENT. <Button
disabled={
!socket.allocationsSummary || discrep.getAmount() !== 0
}
htmlType="submit" htmlType="submit"
> >
{t("jobs.actions.dms.post")} {t("jobs.actions.dms.post")}
</Button> </Button>
<Button
onClick={() => {
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
jobid: job.id,
});
}}
>
Bypass
</Button>
</Space> </Space>
); );
}} }}
</Form.Item> </Form.Item>
</Form> </Form>
</Card>
); );
} }

View File

@@ -13,6 +13,7 @@ import {
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -26,11 +27,17 @@ export function JobsCloseExportButton({
disabled, disabled,
setSelectedJobs, setSelectedJobs,
}) { }) {
const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG); const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleQbxml = async () => { const handleQbxml = async () => {
if (bodyshop.cdk_dealerid) {
history.push(`/manage/dms?jobId=${jobId}`);
return;
}
logImEXEvent("jobs_close_export"); logImEXEvent("jobs_close_export");
setLoading(true); setLoading(true);
@@ -159,12 +166,7 @@ export function JobsCloseExportButton({
}; };
return ( return (
<Button <Button onClick={handleQbxml} loading={loading} disabled={disabled}>
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component"; import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component"; import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
@@ -22,9 +23,10 @@ import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
}); });
export function JobsDetailRates({ jobRO, form, job }) { export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <div>
@@ -77,7 +79,7 @@ export function JobsDetailRates({ jobRO, form, job }) {
label={t("jobs.fields.adjustment_bottom_line")} label={t("jobs.fields.adjustment_bottom_line")}
name="adjustment_bottom_line" name="adjustment_bottom_line"
> >
<CurrencyInput disabled={jobRO} /> <CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item> </Form.Item>
<Space align="end"> <Space align="end">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt"> <Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">

View File

@@ -3,6 +3,7 @@ import React from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
export default function JobsRelatedRos({ jobid, job }) { export default function JobsRelatedRos({ jobid, job }) {
if (!(job && job.vehicle && job.vehicle.jobs)) return null;
return ( return (
<Space wrap> <Space wrap>
{job.vehicle.jobs {job.vehicle.jobs

View File

@@ -57,6 +57,7 @@ export function LaborAllocationsTable({
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center), sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order, state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
render: (text, record) => `${record.cost_center} (${record.mod_lbr_ty})`,
}, },
{ {
title: t("jobs.labels.hrs_total"), title: t("jobs.labels.hrs_total"),

View File

@@ -16,6 +16,7 @@ export const CalculateAllocationsTotals = (
const r = { const r = {
opcode: value, opcode: value,
cost_center: responsibilitycenters.defaults.costs[value], cost_center: responsibilitycenters.defaults.costs[value],
mod_lbr_ty: value,
total: joblines.reduce((acc2, val2) => { total: joblines.reduce((acc2, val2) => {
return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2; return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2;
}, 0), }, 0),

View File

@@ -91,6 +91,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.dms.srcco")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cdk_configuration", "srcco"]}
>
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.generic_customer_number")} label={t("bodyshop.fields.dms.generic_customer_number")}
name={["cdk_configuration", "generic_customer_number"]} name={["cdk_configuration", "generic_customer_number"]}
@@ -99,6 +111,12 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.cashierid")} label={t("bodyshop.fields.dms.cashierid")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["cdk_configuration", "cashierid"]} name={["cdk_configuration", "cashierid"]}
> >
<Input /> <Input />
@@ -113,7 +131,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Form.Item key={field.key}> <Form.Item key={field.key}>
<LayoutFormRow noDivider> <LayoutFormRow noDivider>
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.payer.name")} label={t("jobs.fields.dms.payer.name")}
key={`${index}name`} key={`${index}name`}
name={[field.name, "name"]} name={[field.name, "name"]}
rules={[ rules={[
@@ -125,9 +143,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t( label={t("jobs.fields.dms.payer.dms_acctnumber")}
"bodyshop.fields.dms.payer.dms_acctnumber"
)}
key={`${index}dms_acctnumber`} key={`${index}dms_acctnumber`}
name={[field.name, "dms_acctnumber"]} name={[field.name, "dms_acctnumber"]}
rules={[ rules={[
@@ -139,7 +155,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.payer.control_type")} label={t("jobs.fields.dms.payer.control_type")}
key={`${index}control_type`} key={`${index}control_type`}
name={[field.name, "control_type"]} name={[field.name, "control_type"]}
rules={[ rules={[
@@ -455,6 +471,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
{fields.map((field, index) => ( {fields.map((field, index) => (
<Form.Item key={field.key}> <Form.Item key={field.key}>
<div> <div>
<LayoutFormRow>
<Form.Item <Form.Item
label={t("bodyshop.fields.dms.mappingname")} label={t("bodyshop.fields.dms.mappingname")}
key={`${index}name`} key={`${index}name`}
@@ -468,7 +485,12 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</LayoutFormRow>
<LayoutFormRow <LayoutFormRow
header={t("bodyshop.labels.defaultcostsmapping")} header={t("bodyshop.labels.defaultcostsmapping")}
> >
@@ -1611,12 +1633,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Select> </Select>
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</div> </div>
</Form.Item> </Form.Item>
))} ))}
@@ -2701,7 +2717,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item> </Form.Item>
{bodyshop.cdk_dealerid && ( {bodyshop.cdk_dealerid && (
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_dms_acctnumber")} label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[ rules={[
{ {
required: true, required: true,
@@ -2799,7 +2815,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Form.Item> </Form.Item>
{bodyshop.cdk_dealerid && ( {bodyshop.cdk_dealerid && (
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_dms_acctnumber")} label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[ rules={[
{ {
required: true, required: true,
@@ -2895,21 +2911,9 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_rate")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "taxes", "local", "rate"]}
>
<InputNumber precision={2} />
</Form.Item>
{bodyshop.cdk_dealerid && ( {bodyshop.cdk_dealerid && (
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_dms_acctnumber")} label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[ rules={[
{ {
required: true, required: true,
@@ -2926,6 +2930,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input /> <Input />
</Form.Item> </Form.Item>
)} )}
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_rate")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "taxes", "local", "rate"]}
>
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={<div>AR</div>}> <LayoutFormRow header={<div>AR</div>}>
{/* <Form.Item {/* <Form.Item

View File

@@ -1898,9 +1898,15 @@ export const QUERY_JOB_EXPORT_DMS = gql`
po_number po_number
clm_no clm_no
job_totals job_totals
ownr_fn
ownr_ln
ownr_co_nm
kmin kmin
kmout kmout
v_make_desc
v_model_yr
v_model_desc v_model_desc
area_of_damage
} }
} }
`; `;

View File

@@ -1,10 +1,19 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Col, Result, Row, Select, Space } from "antd"; import {
Button,
Card,
Col,
notification,
Result,
Row,
Select,
Space,
} from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation } from "react-router-dom"; import { useLocation, useHistory } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client"; import SocketIO from "socket.io-client";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
@@ -47,7 +56,8 @@ export const socket = SocketIO(
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) { export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [logLevel, setLogLevel] = useState("TRACE"); const [logLevel, setLogLevel] = useState("DEBUG");
const history = useHistory();
const [logs, setLogs] = useState([]); const [logs, setLogs] = useState([]);
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const { jobId } = search; const { jobId } = search;
@@ -61,6 +71,10 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
document.title = t("titles.dms"); document.title = t("titles.dms");
setSelectedHeader("dms"); setSelectedHeader("dms");
setBreadcrumbs([ setBreadcrumbs([
{
link: "/manage/accounting/receivables",
label: t("titles.bc.accounting-receivables"),
},
{ {
link: "/manage/dms", link: "/manage/dms",
label: t("titles.bc.dms"), label: t("titles.bc.dms"),
@@ -69,9 +83,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
}, [t, setBreadcrumbs, setSelectedHeader]); }, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => { useEffect(() => {
socket.on("connected", () => { socket.on("connect", () => socket.emit("set-log-level", logLevel));
console.log("Connected again.");
});
socket.on("reconnect", () => { socket.on("reconnect", () => {
setLogs((logs) => { setLogs((logs) => {
return [ return [
@@ -90,10 +102,14 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
return [...logs, payload]; return [...logs, payload];
}); });
}); });
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported"),
});
history.push("/manage/accounting/receivables");
});
socket.connect(); if (socket.disconnected) socket.connect();
socket.emit("set-log-level", logLevel);
return () => { return () => {
socket.removeAllListeners(); socket.removeAllListeners();
socket.disconnect(); socket.disconnect();
@@ -101,26 +117,43 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
if (!jobId || !bodyshop.cdk_dealerid || !(data && data.jobs_by_pk))
return <Result status="404" />;
if (loading) return <LoadingSpinner />; if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
if (!jobId || !bodyshop.cdk_dealerid || !(data && data.jobs_by_pk))
return <Result status="404" />;
return ( return (
<div> <div>
<Row gutter={32}> <Row gutter={[16, 16]}>
<Col span={18}> <Col span={10}>
{data && data.jobs_by_pk && data.jobs_by_pk.ro_number} <DmsAllocationsSummary
<DmsAllocationsSummary socket={socket} jobId={jobId} /> title={`${data && data.jobs_by_pk && data.jobs_by_pk.ro_number} | ${
data.jobs_by_pk.ownr_fn || ""
} ${data.jobs_by_pk.ownr_ln || ""} ${
data.jobs_by_pk.ownr_co_nm || ""
} | ${data.jobs_by_pk.v_model_yr || ""} ${
data.jobs_by_pk.v_make_desc || ""
} ${data.jobs_by_pk.v_model_desc || ""}`}
socket={socket}
jobId={jobId}
/>
</Col>
<Col span={14}>
<DmsPostForm <DmsPostForm
socket={socket} socket={socket}
jobId={jobId} jobId={jobId}
job={data && data.jobs_by_pk} job={data && data.jobs_by_pk}
/> />
</Col> </Col>
<Col span={6}>
<Space> <DmsCustomerSelector />
<Col span={24}>
<Card
title={t("jobs.labels.dms.logs")}
extra={
<Space wrap>
<Select <Select
placeholder="Log Level" placeholder="Log Level"
value={logLevel} value={logLevel}
@@ -136,14 +169,22 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
<Select.Option key="ERROR">ERROR</Select.Option> <Select.Option key="ERROR">ERROR</Select.Option>
</Select> </Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button> <Button onClick={() => setLogs([])}>Clear Logs</Button>
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
}}
>
Reconnect
</Button>
</Space> </Space>
<div style={{ maxHeight: "500px", overflowY: "auto" }}> }
>
<DmsLogEvents socket={socket} logs={logs} /> <DmsLogEvents socket={socket} logs={logs} />
</div> </Card>
</Col> </Col>
</Row> </Row>
<DmsCustomerSelector />
</div> </div>
); );
} }

View File

@@ -218,7 +218,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
}, },
]} ]}
> >
<InputNumber disabled={jobRO} /> <InputNumber precision={0} disabled={jobRO} />
</Form.Item> </Form.Item>
)} )}
{bodyshop.cdk_dealerid && ( {bodyshop.cdk_dealerid && (
@@ -244,7 +244,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
}), }),
]} ]}
> >
<InputNumber disabled={jobRO} /> <InputNumber precision={0} disabled={jobRO} />
</Form.Item> </Form.Item>
)} )}
</LayoutFormRow> </LayoutFormRow>

View File

@@ -237,6 +237,9 @@
"dms_acctnumber": "DMS Account #", "dms_acctnumber": "DMS Account #",
"dms_wip_acctnumber": "DMS W.I.P. Account #", "dms_wip_acctnumber": "DMS W.I.P. Account #",
"generic_customer_number": "Generic Customer Number", "generic_customer_number": "Generic Customer Number",
"itc_federal": "Federal Tax is ITC?",
"itc_local": "Local Tax is ITC?",
"itc_state": "State Tax is ITC?",
"mappingname": "DMS Mapping Name", "mappingname": "DMS Mapping Name",
"srcco": "Source Company #/Dealer #" "srcco": "Source Company #/Dealer #"
}, },
@@ -1081,10 +1084,10 @@
"dms": { "dms": {
"addpayer": "Add Payer", "addpayer": "Add Payer",
"createnewcustomer": "Create New Customer", "createnewcustomer": "Create New Customer",
"findmakemodelcode": "", "findmakemodelcode": "Find Make/Model Code",
"getmakes": "Get Makes", "getmakes": "",
"post": "Post", "post": "Post",
"refetchmakesmodels": "", "refetchmakesmodels": "Refetch Make and Model Codes",
"usegeneric": "Use Generic Customer", "usegeneric": "Use Generic Customer",
"useselected": "Use Selected Customer" "useselected": "Use Selected Customer"
}, },
@@ -1146,27 +1149,27 @@
"adjustmenthours": "Adjustment Hours", "adjustmenthours": "Adjustment Hours",
"alt_transport": "Alt. Trans.", "alt_transport": "Alt. Trans.",
"area_of_damage_impact": { "area_of_damage_impact": {
"10": "", "10": "Left Front Side",
"11": "", "11": "Left Front Corner",
"12": "", "12": "Front",
"13": "", "13": "Rollover",
"14": "", "14": "Unknown",
"15": "", "15": "Total Loss",
"16": "", "16": "Non-collision",
"25": "", "25": "Hood",
"26": "", "26": "Deck-lid",
"27": "", "27": "Roof",
"28": "", "28": "Undercarriage",
"34": "", "34": "All Over",
"01": "", "01": "Right Front Corner",
"02": "", "02": "Right Front Side",
"03": "", "03": "Right Side",
"04": "", "04": "Right Rear Side",
"05": "", "05": "Right Rear Corner",
"06": "", "06": "Rear",
"07": "", "07": "Left Rear Corner",
"08": "", "08": "Left Rear Side",
"09": "" "09": "Left Side"
}, },
"ca_bc_pvrt": "PVRT", "ca_bc_pvrt": "PVRT",
"ca_customer_gst": "Customer Portion of GST", "ca_customer_gst": "Customer Portion of GST",
@@ -1199,7 +1202,7 @@
"dms_make": "DMS Make", "dms_make": "DMS Make",
"dms_model": "DMS Model", "dms_model": "DMS Model",
"dms_wip_acctnumber": "Cost WIP DMS Acct #", "dms_wip_acctnumber": "Cost WIP DMS Acct #",
"id": "", "id": "DMS ID",
"journal": "Journal #", "journal": "Journal #",
"name1": "Customer Name", "name1": "Customer Name",
"payer": { "payer": {
@@ -1212,7 +1215,7 @@
"sale": "Sale", "sale": "Sale",
"sale_dms_acctnumber": "Sale DMS Acct #", "sale_dms_acctnumber": "Sale DMS Acct #",
"story": "Story", "story": "Story",
"vinowner": "" "vinowner": "VIN Owner"
}, },
"driveable": "Driveable", "driveable": "Driveable",
"employee_body": "Body", "employee_body": "Body",
@@ -1434,11 +1437,11 @@
"difference": "Difference", "difference": "Difference",
"diskscan": "Scan Disk for Estimates", "diskscan": "Scan Disk for Estimates",
"dms": { "dms": {
"defaultstory": "", "defaultstory": "Bodyshop RO {{ro_number}}. Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).",
"kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.", "kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.",
"logs": "", "logs": "Logs",
"notallocated": "Not Allocated", "notallocated": "Not Allocated",
"postingform": "", "postingform": "Posting Form",
"totalallocated": "Total Amount Allocated" "totalallocated": "Total Amount Allocated"
}, },
"documents": "Documents", "documents": "Documents",
@@ -2282,7 +2285,7 @@
"courtesycars-detail": "Courtesy Car {{number}}", "courtesycars-detail": "Courtesy Car {{number}}",
"courtesycars-new": "New Courtesy Car", "courtesycars-new": "New Courtesy Car",
"dashboard": "Dashboard", "dashboard": "Dashboard",
"dms": "", "dms": "DMS Export",
"export-logs": "Export Logs", "export-logs": "Export Logs",
"jobs": "Jobs", "jobs": "Jobs",
"jobs-active": "Active Jobs", "jobs-active": "Active Jobs",
@@ -2321,7 +2324,7 @@
"courtesycars-create": "New Courtesy Car | $t(titles.app)", "courtesycars-create": "New Courtesy Car | $t(titles.app)",
"courtesycars-detail": "Courtesy Car {{id}} | $t(titles.app)", "courtesycars-detail": "Courtesy Car {{id}} | $t(titles.app)",
"dashboard": "Dashboard | $t(titles.app)", "dashboard": "Dashboard | $t(titles.app)",
"dms": "", "dms": "DMS Export | $t(titles.app)",
"export-logs": "Export Logs | $t(titles.app)", "export-logs": "Export Logs | $t(titles.app)",
"jobs": "Active Jobs | $t(titles.app)", "jobs": "Active Jobs | $t(titles.app)",
"jobs-admin": "Job {{ro_number}} - Admin | $t(titles.app)", "jobs-admin": "Job {{ro_number}} - Admin | $t(titles.app)",

View File

@@ -237,6 +237,9 @@
"dms_acctnumber": "", "dms_acctnumber": "",
"dms_wip_acctnumber": "", "dms_wip_acctnumber": "",
"generic_customer_number": "", "generic_customer_number": "",
"itc_federal": "",
"itc_local": "",
"itc_state": "",
"mappingname": "", "mappingname": "",
"srcco": "" "srcco": ""
}, },

View File

@@ -237,6 +237,9 @@
"dms_acctnumber": "", "dms_acctnumber": "",
"dms_wip_acctnumber": "", "dms_wip_acctnumber": "",
"generic_customer_number": "", "generic_customer_number": "",
"itc_federal": "",
"itc_local": "",
"itc_state": "",
"mappingname": "", "mappingname": "",
"srcco": "" "srcco": ""
}, },

View File

@@ -47,17 +47,41 @@ exports.default = async function (socket, jobid) {
}, },
}; };
//Determine if there are MAPA and MASH lines already on the estimate.
//If there are, don't do anything extra (mitchell estimate)
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
let hasMapaLine = false;
let hasMashLine = false;
const profitCenterHash = job.joblines.reduce((acc, val) => { const profitCenterHash = job.joblines.reduce((acc, val) => {
//Check the Parts Assignment //Check the Parts Assignment
if (val.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
hasMapaLine = true;
}
if (val.db_ref === "936007") {
hasMashLine = true;
}
if (val.profitcenter_part) { if (val.profitcenter_part) {
if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero(); if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();
acc[val.profitcenter_part] = acc[val.profitcenter_part].add( let DineroAmount = Dinero({
Dinero({ amount: Math.round(val.act_price * 100),
amount: Math.round((val.act_price || 0) * 100), }).multiply(val.part_qty || 1);
}).multiply(val.part_qty || 0)
if (val.prt_dsmk_p && val.prt_dsmk_p !== 0) {
// console.log("Have a part discount", val);
DineroAmount = DineroAmount.add(
DineroAmount.percentage(Math.abs(val.prt_dsmk_p || 0)).multiply(
val.prt_dsmk_p > 0 ? 1 : -1
)
); );
} }
acc[val.profitcenter_part] =
acc[val.profitcenter_part].add(DineroAmount);
}
if (val.profitcenter_labor) { if (val.profitcenter_labor) {
//Check the Labor Assignment. //Check the Labor Assignment.
@@ -79,7 +103,8 @@ exports.default = async function (socket, jobid) {
bill_val.billlines.map((line_val) => { bill_val.billlines.map((line_val) => {
if (!bill_acc[line_val.cost_center]) if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero(); bill_acc[line_val.cost_center] = Dinero();
const lineDinero = Dinero({
let lineDinero = Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100), amount: Math.round((line_val.actual_cost || 0) * 100),
}) })
.multiply(line_val.quantity) .multiply(line_val.quantity)
@@ -87,32 +112,73 @@ exports.default = async function (socket, jobid) {
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center] =
bill_acc[line_val.cost_center].add(lineDinero); bill_acc[line_val.cost_center].add(lineDinero);
//Add appropriate tax amounts.
const {
applicable_taxes: { local, state, federal },
} = line_val;
if (local) {
taxAllocations.local.cost = taxAllocations.local.cost.add(
lineDinero.percentage(bill_val.local_tax_rate || 0)
);
}
if (state) {
taxAllocations.state.cost = taxAllocations.state.cost.add(
lineDinero.percentage(bill_val.state_tax_rate || 0)
);
}
if (federal) {
taxAllocations.federal.cost = taxAllocations.federal.cost.add(
lineDinero.percentage(bill_val.federal_tax_rate || 0)
);
}
return null; return null;
}); });
return bill_acc; return bill_acc;
}, {}); }, {});
job.timetickets.forEach((ticket) => {
//Get the total amount of the ticket.
let TicketTotal = Dinero({
amount: Math.round(
ticket.rate *
(ticket.employee && ticket.employee.flat_rate
? ticket.productivehrs || 0
: ticket.actualhrs || 0) *
100
),
});
//Add it to the right cost center.
if (!costCenterHash[ticket.cost_center])
costCenterHash[ticket.cost_center] = Dinero();
costCenterHash[ticket.cost_center] =
costCenterHash[ticket.cost_center].add(TicketTotal);
});
if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
// console.log("Adding MAPA Line Manually.");
const mapaAccountName =
bodyshop.md_responsibility_centers.defaults.profits.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.profits.find(
(c) => c.name === mapaAccountName
);
if (mapaAccount) {
if (!profitCenterHash[mapaAccountName])
profitCenterHash[mapaAccountName] = Dinero();
profitCenterHash[mapaAccountName] = profitCenterHash[
mapaAccountName
].add(Dinero(job.job_totals.rates.mapa.total));
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
}
}
if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) {
// console.log("Adding MASH Line Manually.");
const mashAccountName =
bodyshop.md_responsibility_centers.defaults.profits.MASH;
const mashAccount = bodyshop.md_responsibility_centers.profits.find(
(c) => c.name === mashAccountName
);
if (mashAccount) {
if (!profitCenterHash[mashAccountName])
profitCenterHash[mashAccountName] = Dinero();
profitCenterHash[mashAccountName] = profitCenterHash[
mashAccountName
].add(Dinero(job.job_totals.rates.mash.total));
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
const jobAllocations = _.union( const jobAllocations = _.union(
Object.keys(profitCenterHash), Object.keys(profitCenterHash),
Object.keys(costCenterHash) Object.keys(costCenterHash)
@@ -141,7 +207,9 @@ exports.default = async function (socket, jobid) {
taxAllocations[key].sale.getAmount() > 0 || taxAllocations[key].sale.getAmount() > 0 ||
taxAllocations[key].cost.getAmount() > 0 taxAllocations[key].cost.getAmount() > 0
) )
.map((key) => taxAllocations[key]), .map((key) => {
return { ...taxAllocations[key], tax: key };
}),
]; ];
} catch (error) { } catch (error) {
CdkBase.createLogEvent( CdkBase.createLogEvent(

View File

@@ -8,14 +8,11 @@ require("dotenv").config({
const GraphQLClient = require("graphql-request").GraphQLClient; const GraphQLClient = require("graphql-request").GraphQLClient;
const soap = require("soap"); const soap = require("soap");
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const CdkBase = require("../web-sockets/web-socket");
const CdkWsdl = require("./cdk-wsdl").default; const CdkWsdl = require("./cdk-wsdl").default;
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const Dinero = require("dinero.js");
const _ = require("lodash");
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const { performance } = require("perf_hooks");
const apiGqlClient = require("../graphql-client/graphql-client").client;
// exports.default = async function (socket, cdk_dealerid) { // exports.default = async function (socket, cdk_dealerid) {
// try { // try {
@@ -87,6 +84,7 @@ exports.default = async function ReloadCdkMakes(req, res) {
count: newList.length, count: newList.length,
} }
); );
res.sendStatus(200);
} catch (error) { } catch (error) {
logger.log( logger.log(
"cdk-replace-makes-models-error", "cdk-replace-makes-models-error",
@@ -98,6 +96,7 @@ exports.default = async function ReloadCdkMakes(req, res) {
error, error,
} }
); );
res.status(500).json(error);
} }
}; };
@@ -108,7 +107,7 @@ async function GetCdkMakes(req, cdk_dealerid) {
try { try {
const soapClientVehicleInsert = await soap.createClientAsync( const soapClientVehicleInsert = await soap.createClientAsync(
CdkWsdl.VehicleInsert CdkWsdl.VehicleInsertUpdate
); );
const soapResponseVehicleSearch = const soapResponseVehicleSearch =

View File

@@ -11,6 +11,8 @@ const queries = require("../graphql-client/queries");
const CdkBase = require("../web-sockets/web-socket"); const CdkBase = require("../web-sockets/web-socket");
const CdkWsdl = require("./cdk-wsdl").default; const CdkWsdl = require("./cdk-wsdl").default;
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl"); const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const CalcualteAllocations = require("./cdk-calculate-allocations").default;
const moment = require("moment"); const moment = require("moment");
exports.default = async function (socket, { txEnvelope, jobid }) { exports.default = async function (socket, { txEnvelope, jobid }) {
@@ -44,7 +46,7 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
"DEBUG", "DEBUG",
`{2.1} Querying the Vehicle using the DMSVid: ${socket.DMSVid}` `{2.1} Querying the Vehicle using the DMSVid: ${socket.DMSVid.vehiclesVehId}`
); );
socket.DMSVeh = await QueryDmsVehicleById(socket, JobData, socket.DMSVid); socket.DMSVeh = await QueryDmsVehicleById(socket, JobData, socket.DMSVid);
@@ -73,7 +75,9 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
socket.DMSCustList = await QueryDmsCustomerByName(socket, JobData); socket.DMSCustList = await QueryDmsCustomerByName(socket, JobData);
socket.emit("cdk-select-customer", [ socket.emit("cdk-select-customer", [
...(socket.DMSVehCustomer ? [socket.DMSVehCustomer] : []), ...(socket.DMSVehCustomer
? [{ ...socket.DMSVehCustomer, vinOwner: true }]
: []),
...socket.DMSCustList, ...socket.DMSCustList,
]); ]);
} catch (error) { } catch (error) {
@@ -82,14 +86,6 @@ exports.default = async function (socket, { txEnvelope, jobid }) {
"ERROR", "ERROR",
`Error encountered in CdkJobExport. ${error}` `Error encountered in CdkJobExport. ${error}`
); );
} finally {
//Ensure we always insert logEvents
//GQL to insert logevents.
CdkBase.createLogEvent(
socket,
"DEBUG",
`Capturing log events to database.`
);
} }
}; };
@@ -146,18 +142,80 @@ async function CdkSelectedCustomer(socket, selectedCustomerId) {
); );
socket.DMSVeh = await UpdateDmsVehicle(socket); socket.DMSVeh = await UpdateDmsVehicle(socket);
} }
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
"DEBUG", "DEBUG",
`{5}Updating Service Vehicle History.` `{5} Creating Transaction header with Dms Start WIP`
); );
await InsertServiceVehicleHistory(socket); socket.DMSTransHeader = await InsertDmsStartWip(socket);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{5.1} Creating Transaction with ID ${socket.DMSTransHeader.transID}`
);
socket.DMSBatchTxn = await InsertDmsBatchWip(socket);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6} Attempting to post Transaction with ID ${socket.DMSTransHeader.transID}`
);
socket.DmsBatchTxnPost = await PostDmsBatchWip(socket);
if (socket.DmsBatchTxnPost.code === "success") {
//something
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6} Successfully posted sransaction to DMS.`
);
await MarkJobExported(socket, socket.JobData.id);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{5} Updating Service Vehicle History. ***SKIPPING FOR NOW TO PRESERVE RO NUMBERS ***`
);
//socket.DMSVehHistory = await InsertServiceVehicleHistory(socket);
socket.emit("export-success", socket.JobData.id);
} else {
//Get the error code
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6.1} Getting errors for Transaction ID ${socket.DMSTransHeader.transID}`
);
socket.DmsError = await QueryDmsErrWip(socket);
//Delete the transaction
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6.2} Deleting Transaction ID ${socket.DMSTransHeader.transID}`
);
socket.DmsBatchTxnPost = await DeleteDmsWip(socket);
//Emit the error in a nice way .
socket.DmsError.errMsg
.split("|")
.map(
(e) =>
e !== null &&
e !== "" &&
CdkBase.createLogEvent(
socket,
"ERROR",
`Error(s) encountered in posting transaction. ${e}`
)
);
}
} catch (error) { } catch (error) {
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
"ERROR", "ERROR",
`Error encountered in CdkSelectedCustomer. ${error}` `Error encountered in CdkSelectedCustomer. ${error}`
); );
await InsertFailedExportLog(socket, error);
} finally { } finally {
//Ensure we always insert logEvents //Ensure we always insert logEvents
//GQL to insert logevents. //GQL to insert logevents.
@@ -509,7 +567,7 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
await soapClientCustomerInsertUpdate.insertAsync( await soapClientCustomerInsertUpdate.insertAsync(
{ {
arg0: CDK_CREDENTIALS, arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards. arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: { userId: null }, arg2: { userId: null },
arg3: { arg3: {
//Copied the required fields from the other integration. //Copied the required fields from the other integration.
@@ -519,7 +577,12 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
addressLine: socket.JobData.ownr_addr1, addressLine: socket.JobData.ownr_addr1,
city: socket.JobData.ownr_city, city: socket.JobData.ownr_city,
country: null, country: null,
postalCode: socket.JobData.ownr_zip, postalCode:
socket.JobData.ownr_zip &&
socket.JobData.ownr_zip //TODO Need to remove for US Based customers.
.toUpperCase()
.replace(/\W/g, "")
.replace(/(...)/, "$1 "),
stateOrProvince: socket.JobData.ownr_st, stateOrProvince: socket.JobData.ownr_st,
}, },
contactInfo: { contactInfo: {
@@ -527,6 +590,10 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
main: true, main: true,
value: socket.JobData.ownr_ph1, value: socket.JobData.ownr_ph1,
}, },
email: {
desc: socket.JobData.ownr_ea ? "Other" : "CustomerDeclined",
value: socket.JobData.ownr_ea ? "Other" : null,
},
}, },
demographics: null, demographics: null,
name1: { name1: {
@@ -539,6 +606,10 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
suffix: null, suffix: null,
title: null, title: null,
}, },
//TODO - REMOVE THIS AFTER TESTING.
...(process.env.NODE_ENV !== "production"
? { arStatus: { dealerField1: "Testing" } }
: {}),
}, },
}, },
@@ -751,7 +822,7 @@ async function InsertServiceVehicleHistory(socket) {
closeDate: moment(socket.JobData.invoice_date).format("YYYY-MM-DD"), closeDate: moment(socket.JobData.invoice_date).format("YYYY-MM-DD"),
closeTime: moment(socket.JobData.invoice_date).format("HH:MM:SS"), closeTime: moment(socket.JobData.invoice_date).format("HH:MM:SS"),
comments: socket.txEnvelope.story, comments: socket.txEnvelope.story,
cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid, //NEEDS TO BE PROVIDED BY DEALER. cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid,
}, },
}); });
@@ -779,8 +850,7 @@ async function InsertServiceVehicleHistory(socket) {
`soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.` `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`
); );
CheckCdkResponseForError(socket, soapResponseServiceHistoryInsert); CheckCdkResponseForError(socket, soapResponseServiceHistoryInsert);
const VehicleFromDMS = result && result.return && result.return.vehicle; return result && result.return;
return VehicleFromDMS;
} catch (error) { } catch (error) {
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
@@ -797,19 +867,20 @@ async function InsertDmsStartWip(socket) {
); );
const soapResponseAccountingGLInsertUpdate = const soapResponseAccountingGLInsertUpdate =
await soapClientAccountingGLInsertUpdate.startWIPAsync({ await soapClientAccountingGLInsertUpdate.doStartWIPAsync({
arg0: CDK_CREDENTIALS, arg0: CDK_CREDENTIALS,
arg1: { id: socket.JobData.bodyshop.cdk_dealerid }, arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: { arg2: {
acctgDate: moment().toISOString(), acctgDate: moment().format("YYYY-MM-DD"),
//socket.JobData.invoice_date
desc: socket.txEnvelope.story, desc: socket.txEnvelope.story,
docType: 7 || 10, //Need to check what this usually would be? docType: 10 || 7, //Need to check what this usually would be? Apparently it is almost always 10 or 7.
//1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge, 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo //1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge, 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo
m13Flag: 0, m13Flag: 0,
refer: socket.JobData.ro_number, refer: socket.JobData.ro_number,
srcCo: socket.txEnvelope.journal, srcCo: socket.JobData.bodyshop.cdk_configuration.srcco,
srcJrnl: socket.txEnvelope.journal, srcJrnl: socket.txEnvelope.journal,
userID: "?", //Where is this coming from? userID: socket.JobData.bodyshop.cdk_configuration.cashierid, //Where is this coming from?
//userName: "IMEX", //userName: "IMEX",
}, },
}); });
@@ -820,13 +891,13 @@ async function InsertDmsStartWip(socket) {
CdkBase.createXmlEvent( CdkBase.createXmlEvent(
socket, socket,
rawRequest, rawRequest,
`soapClientAccountingGLInsertUpdate.startWIPAsync request.` `soapClientAccountingGLInsertUpdate.doStartWIPAsync request.`
); );
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
"TRACE", "TRACE",
`soapClientAccountingGLInsertUpdate.startWIPAsync Result ${JSON.stringify( `soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify(
result, result,
null, null,
2 2
@@ -835,17 +906,383 @@ async function InsertDmsStartWip(socket) {
CdkBase.createXmlEvent( CdkBase.createXmlEvent(
socket, socket,
rawResponse, rawResponse,
`soapClientAccountingGLInsertUpdate.startWIPAsync response.` `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`
); );
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate); CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
const VehicleFromDMS = result && result.return && result.return.vehicle; const TransactionHeader = result && result.return;
return VehicleFromDMS; return TransactionHeader;
} catch (error) { } catch (error) {
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
"ERROR", "ERROR",
`Error in QueryDmsVehicleById - ${error}` `Error in InsertDmsStartWip - ${error}`
); );
throw new Error(error); throw new Error(error);
} }
} }
async function InsertDmsBatchWip(socket) {
try {
const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(
CdkWsdl.AccountingGLInsertUpdate
);
const soapResponseAccountingGLInsertUpdate =
await soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync({
arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: {
transWIPs: await GenerateTransWips(socket),
},
});
const [result, rawResponse, , rawRequest] =
soapResponseAccountingGLInsertUpdate;
CdkBase.createXmlEvent(
socket,
rawRequest,
`soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync request.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
CdkBase.createXmlEvent(
socket,
rawResponse,
`soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`
);
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
const BatchWipResult = result && result.return;
return BatchWipResult;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in InsertDmsBatchWip - ${error}`
);
throw new Error(error);
}
}
async function GenerateTransWips(socket) {
const allocations = await CalcualteAllocations(socket, socket.JobData.id);
const wips = [];
allocations.forEach((alloc) => {
//Add the sale item from each allocation.
if (alloc.sale.getAmount() > 0 && !alloc.tax) {
const item = {
acct: alloc.profitCenter.dms_acctnumber,
cntl: socket.JobData.ro_number,
cntl2: null,
credtMemoNo: null,
postAmt: alloc.sale.multiply(-1).getAmount(),
postDesc: null,
prod: null,
statCnt: 1,
transID: socket.DMSTransHeader.transID,
trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
};
wips.push(item);
}
//Add the cost Item.
if (alloc.cost.getAmount() > 0 && !alloc.tax) {
const item = {
acct: alloc.costCenter.dms_acctnumber,
cntl: socket.JobData.ro_number,
cntl2: null,
credtMemoNo: null,
postAmt: alloc.cost.getAmount(),
postDesc: null,
prod: null,
statCnt: 1,
transID: socket.DMSTransHeader.transID,
trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
};
wips.push(item);
const itemWip = {
acct: alloc.costCenter.dms_wip_acctnumber,
cntl: socket.JobData.ro_number,
cntl2: null,
credtMemoNo: null,
postAmt: alloc.cost.multiply(-1).getAmount(),
postDesc: null,
prod: null,
statCnt: 1,
transID: socket.DMSTransHeader.transID,
trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
};
wips.push(itemWip);
//Add to the WIP account.
}
if (alloc.tax) {
// if (alloc.cost.getAmount() > 0) {
// const item = {
// acct: alloc.costCenter.dms_acctnumber,
// cntl: socket.JobData.ro_number,
// cntl2: null,
// credtMemoNo: null,
// postAmt: alloc.cost.getAmount(),
// postDesc: null,
// prod: null,
// statCnt: 1,
// transID: socket.DMSTransHeader.transID,
// trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
// };
// wips.push(item);
// }
if (alloc.sale.getAmount() > 0) {
const item2 = {
acct: alloc.profitCenter.dms_acctnumber,
cntl: socket.JobData.ro_number,
cntl2: null,
credtMemoNo: null,
postAmt: alloc.sale.multiply(-1).getAmount(),
postDesc: null,
prod: null,
statCnt: 1,
transID: socket.DMSTransHeader.transID,
trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
};
wips.push(item2);
}
}
});
socket.txEnvelope.payers.forEach((payer) => {
const item = {
acct: payer.dms_acctnumber,
cntl: payer.controlnumber,
cntl2: null,
credtMemoNo: null,
postAmt: Math.round(payer.amount * 100),
postDesc: null,
prod: null,
statCnt: 1,
transID: socket.DMSTransHeader.transID,
trgtCoID: socket.JobData.bodyshop.cdk_configuration.srcco,
};
wips.push(item);
});
//should validate that the wips = 0
console.log(
"WIPS TOTAL",
wips.reduce((acc, val) => {
console.log(val);
console.log(acc + val.postAmt);
return acc + val.postAmt;
}, 0)
);
return wips;
}
async function PostDmsBatchWip(socket) {
try {
const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(
CdkWsdl.AccountingGLInsertUpdate
);
const soapResponseAccountingGLInsertUpdate =
await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({
arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: {
postWIP: { opCode: "P", transID: socket.DMSTransHeader.transID },
},
});
const [result, rawResponse, , rawRequest] =
soapResponseAccountingGLInsertUpdate;
CdkBase.createXmlEvent(
socket,
rawRequest,
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
CdkBase.createXmlEvent(
socket,
rawResponse,
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`
);
// CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
const PostResult = result && result.return;
return PostResult;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in PostDmsBatchWip - ${error}`
);
throw new Error(error);
}
}
async function QueryDmsErrWip(socket) {
try {
const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(
CdkWsdl.AccountingGLInsertUpdate
);
const soapResponseAccountingGLInsertUpdate =
await soapClientAccountingGLInsertUpdate.doErrWIPAsync({
arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: socket.DMSTransHeader.transID,
});
const [result, rawResponse, , rawRequest] =
soapResponseAccountingGLInsertUpdate;
CdkBase.createXmlEvent(
socket,
rawRequest,
`soapClientAccountingGLInsertUpdate.doErrWIPAsync request.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
CdkBase.createXmlEvent(
socket,
rawResponse,
`soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`
);
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
const PostResult = result && result.return;
return PostResult;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in QueryDmsErrWip - ${error}`
);
throw new Error(error);
}
}
async function DeleteDmsWip(socket) {
try {
const soapClientAccountingGLInsertUpdate = await soap.createClientAsync(
CdkWsdl.AccountingGLInsertUpdate
);
const soapResponseAccountingGLInsertUpdate =
await soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync({
arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: {
postWIP: { opCode: "D", transID: socket.DMSTransHeader.transID },
},
});
const [result, rawResponse, , rawRequest] =
soapResponseAccountingGLInsertUpdate;
CdkBase.createXmlEvent(
socket,
rawRequest,
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync request.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
CdkBase.createXmlEvent(
socket,
rawResponse,
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`
);
CheckCdkResponseForError(socket, soapResponseAccountingGLInsertUpdate);
const PostResult = result && result.return;
return PostResult;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in PostDmsBatchWip - ${error}`
);
throw new Error(error);
}
}
async function MarkJobExported(socket, jobid) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Marking job as exported for id ${jobid}`
);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_JOB_EXPORTED, {
jobId: jobid,
job: {
status:
socket.JobData.bodyshop.md_ro_statuses.default_exported ||
"Exported*",
date_exported: new Date(),
},
log: {
bodyshopid: socket.JobData.bodyshop.id,
jobid: jobid,
successful: true,
useremail: socket.user.email,
},
});
return result;
}
async function InsertFailedExportLog(socket, error) {
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.INSERT_EXPORT_LOG, {
log: {
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: [error],
useremail: socket.user.email,
},
});
return result;
}

View File

@@ -78,6 +78,7 @@ exports.checkIndividualResult = checkIndividualResult;
const cdkDomain = "https://uat-3pa.dmotorworks.com"; const cdkDomain = "https://uat-3pa.dmotorworks.com";
exports.default = { exports.default = {
// VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`, // VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`,
HelpDataBase: `${cdkDomain}/pip-help-database-location/services/HelpDatabaseLocation?wsdl`,
AccountingGLInsertUpdate: `${cdkDomain}/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl`, AccountingGLInsertUpdate: `${cdkDomain}/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl`,
VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`, VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`,
CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`, CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`,

View File

@@ -140,6 +140,7 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
ownr_zip ownr_zip
ownr_city ownr_city
ownr_st ownr_st
ownr_ea
ins_co_nm ins_co_nm
job_totals job_totals
rate_la1 rate_la1
@@ -177,9 +178,11 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
ca_customer_gst ca_customer_gst
bodyshop { bodyshop {
id id
md_ro_statuses
md_responsibility_centers md_responsibility_centers
accountingconfig accountingconfig
cdk_dealerid cdk_dealerid
cdk_configuration
} }
owner { owner {
accountingid accountingid
@@ -951,12 +954,12 @@ affected_rows
`; `;
exports.GET_CDK_ALLOCATIONS = ` exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) { jobs_by_pk(id: $id) {
bodyshop { bodyshop {
id id
md_responsibility_centers md_responsibility_centers
cdk_configuration
} }
ro_number ro_number
invoice_allocation invoice_allocation
@@ -1013,7 +1016,17 @@ exports.GET_CDK_ALLOCATIONS = `
actual_delivery actual_delivery
scheduled_in scheduled_in
actual_in actual_in
bills { timetickets {
id
actualhrs
cost_center
productivehrs
rate
employee {
flat_rate
}
}
bills(where: {isinhouse: {_eq: false}}) {
id id
federal_tax_rate federal_tax_rate
local_tax_rate local_tax_rate
@@ -1037,6 +1050,7 @@ exports.GET_CDK_ALLOCATIONS = `
part_type part_type
oem_partno oem_partno
db_price db_price
db_ref
act_price act_price
part_qty part_qty
mod_lbr_ty mod_lbr_ty
@@ -1066,3 +1080,29 @@ exports.SET_QBO_AUTH = `mutation SET_QBO_AUTH($email: String!, $qbo_auth: jsonb!
} }
} }
`; `;
exports.MARK_JOB_EXPORTED = `
mutation MARK_JOB_EXPORTED($jobId: uuid!, $job: jobs_set_input!, $log: exportlog_insert_input!) {
update_jobs(where: {id: {_eq: $jobId}}, _set: $job) {
returning {
id
date_exported
status
alt_transport
ro_number
production_vars
lbr_adjustments
}
}
insert_exportlog_one(object: $log) {
id
}
}
`;
exports.INSERT_EXPORT_LOG = `
mutation INSERT_EXPORT_LOG($log: exportlog_insert_input!) {
insert_exportlog_one(object: $log) {
id
}
}
`;

View File

@@ -50,7 +50,11 @@ io.on("connection", (socket) => {
socket.on("set-log-level", (level) => { socket.on("set-log-level", (level) => {
socket.log_level = level; socket.log_level = level;
createLogEvent(socket, "DEBUG", `Updated log level to ${level}`); socket.emit("log-event", {
timestamp: new Date(),
level: "INFO",
message: `Updated log level to ${level}`,
});
}); });
socket.on("cdk-export-job", (jobid) => { socket.on("cdk-export-job", (jobid) => {