Merged in feature/2021-06-18 (pull request #109)

Feature/2021 06 18
This commit is contained in:
Patrick Fic
2021-06-15 23:42:24 +00:00
29 changed files with 991 additions and 134 deletions

View File

@@ -3753,6 +3753,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>md_jobline_presets</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>md_payment_types</name> <name>md_payment_types</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -10532,6 +10553,27 @@
<folder_node> <folder_node>
<name>errors</name> <name>errors</name>
<children> <children>
<concept_node>
<name>refreshrequired</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>updatinglayout</name> <name>updatinglayout</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -15803,6 +15845,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>presets</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>
@@ -24413,6 +24476,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>newjob</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>owners</name> <name>owners</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -32,13 +32,9 @@ export default function DashboardMonthlyLaborSales({ data, ...cardProps }) {
return { return {
name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`), name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`),
value: laborData[key].getAmount() / 100, value: laborData[key].getAmount() / 100,
// color: pieColor(i.status), color: pieColor(key.toUpperCase()),
}; };
}); });
console.log(
"🚀 ~ file: monthly-parts-sales.component.jsx ~ line 34 ~ chartData",
chartData
);
return ( return (
<Card title={t("dashboard.titles.monthlylaborsales")} {...cardProps}> <Card title={t("dashboard.titles.monthlylaborsales")} {...cardProps}>
@@ -72,36 +68,56 @@ export const DashboardMonthlyRevenueGraphGql = `
`; `;
const pieColor = (type) => {
if (type === "LAA") return "lightgreen";
else if (type === "LAB") return "dodgerblue";
else if (type === "LAD") return "aliceblue";
else if (type === "LAE") return "seafoam";
else if (type === "LAG") return "chartreuse";
else if (type === "LAF") return "magenta";
else if (type === "LAM") return "gold";
else if (type === "LAR") return "crimson";
else if (type === "LAU") return "slategray";
else if (type === "LA1") return "slategray";
else if (type === "LA2") return "slategray";
else if (type === "LA3") return "slategray";
else if (type === "LA4") return "slategray";
return "slategray";
};
const renderActiveShape = (props) => { const renderActiveShape = (props) => {
const RADIAN = Math.PI / 180; //const RADIAN = Math.PI / 180;
const { const {
cx, cx,
cy, cy,
midAngle, //midAngle,
innerRadius, innerRadius,
outerRadius, outerRadius,
startAngle, startAngle,
endAngle, endAngle,
fill, fill,
payload, payload,
percent, // percent,
value, value,
} = props; } = props;
const sin = Math.sin(-RADIAN * midAngle); // const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle); // const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos; // // const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin; // const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos; // const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin; // const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22; // //const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my; // const ey = my;
const textAnchor = cos >= 0 ? "start" : "end"; //const textAnchor = cos >= 0 ? "start" : "end";
return ( return (
<g> <g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}> <text x={cx} y={cy} dy={0} textAnchor="middle" fill={fill}>
{payload.name} {payload.name}
</text> </text>
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
</text>
<Sector <Sector
cx={cx} cx={cx}
cy={cy} cy={cy}
@@ -120,29 +136,28 @@ const renderActiveShape = (props) => {
outerRadius={outerRadius + 10} outerRadius={outerRadius + 10}
fill={fill} fill={fill}
/> />
<path
d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
stroke={fill}
fill="none"
/>
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
textAnchor={textAnchor}
fill="#333"
>
{payload.name}
</text>
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
dy={18}
textAnchor={textAnchor}
fill="#999"
>
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
</text>
</g> </g>
); );
}; };
// <path
// d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
// stroke={fill}
// fill="none"
// />;
// <text
// x={ex + (cos >= 0 ? 1 : -1) * 12}
// y={ey}
// textAnchor={textAnchor}
// fill="#333"
// >
// {payload.name}
// </text>
// <text
// x={ex + (cos >= 0 ? 1 : -1) * 12}
// y={ey}
// dy={18}
// textAnchor={textAnchor}
// fill="#999"
// >
// {Dinero({ amount: Math.round(value * 100) }).toFormat()}
// </text>

View File

@@ -29,13 +29,9 @@ export default function DashboardMonthlyPartsSales({ data, ...cardProps }) {
return { return {
name: t(`joblines.fields.part_types.${key.toUpperCase()}`), name: t(`joblines.fields.part_types.${key.toUpperCase()}`),
value: partData[key].getAmount() / 100, value: partData[key].getAmount() / 100,
// color: pieColor(i.status), color: pieColor(key.toUpperCase()),
}; };
}); });
console.log(
"🚀 ~ file: monthly-parts-sales.component.jsx ~ line 34 ~ chartData",
chartData
);
return ( return (
<Card title={t("dashboard.titles.monthlypartssales")} {...cardProps}> <Card title={t("dashboard.titles.monthlypartssales")} {...cardProps}>
@@ -68,37 +64,55 @@ export default function DashboardMonthlyPartsSales({ data, ...cardProps }) {
export const DashboardMonthlyRevenueGraphGql = ` export const DashboardMonthlyRevenueGraphGql = `
`; `;
const pieColor = (type) => {
if (type === "PAA") return "darkgreen";
else if (type === "PAC") return "green";
else if (type === "PAE") return "gold";
else if (type === "PAG") return "seafoam";
else if (type === "PAL") return "chartreuse";
else if (type === "PAM") return "magenta";
else if (type === "PAN") return "crimson";
else if (type === "PAO") return "gold";
else if (type === "PAP") return "crimson";
else if (type === "PAR") return "indigo";
else if (type === "PAS") return "dodgerblue";
else if (type === "PASL") return "dodgerblue";
return "slategray";
};
const renderActiveShape = (props) => { const renderActiveShape = (props) => {
const RADIAN = Math.PI / 180; // const RADIAN = Math.PI / 180;
const { const {
cx, cx,
cy, cy,
midAngle, // midAngle,
innerRadius, innerRadius,
outerRadius, outerRadius,
startAngle, startAngle,
endAngle, endAngle,
fill, fill,
payload, payload,
percent, // percent,
value, value,
} = props; } = props;
const sin = Math.sin(-RADIAN * midAngle); // const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle); // const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos; // const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin; //const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos; // const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin; //const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22; // const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my; // const ey = my;
const textAnchor = cos >= 0 ? "start" : "end"; // const textAnchor = cos >= 0 ? "start" : "end";
return ( return (
<g> <g>
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}> <text x={cx} y={cy} dy={0} textAnchor="middle" fill={fill}>
{payload.name} {payload.name}
</text> </text>
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
</text>
<Sector <Sector
cx={cx} cx={cx}
cy={cy} cy={cy}
@@ -117,29 +131,6 @@ const renderActiveShape = (props) => {
outerRadius={outerRadius + 10} outerRadius={outerRadius + 10}
fill={fill} fill={fill}
/> />
<path
d={`M${sx},${sy}L${mx},${my}L${ex},${ey}`}
stroke={fill}
fill="none"
/>
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
textAnchor={textAnchor}
fill="#333"
>
{payload.name}
</text>
<text
x={ex + (cos >= 0 ? 1 : -1) * 12}
y={ey}
dy={18}
textAnchor={textAnchor}
fill="#999"
>
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
</text>
</g> </g>
); );
}; };

View File

@@ -69,8 +69,8 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
type="monotone" type="monotone"
name="Accumulated Sales" name="Accumulated Sales"
dataKey="accSales" dataKey="accSales"
fill="#8884d8" fill="#3CB371"
stroke="#8884d8" stroke="#3CB371"
/> />
<Bar <Bar
name="Daily Sales" name="Daily Sales"

View File

@@ -1,8 +1,8 @@
import { Card, Typography } from "antd";
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import { Card } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export default function DashboardRefreshRequired(props) { export default function DashboardRefreshRequired(props) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -11,14 +11,14 @@ export default function DashboardRefreshRequired(props) {
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
textOverflow: "ellipsis",
}} }}
> >
<SyncOutlined /> <SyncOutlined style={{ fontSize: "300%", margin: "1rem" }} />
<Typography.Title level={4}> <div>{t("dashboard.errors.refreshrequired")}</div>
{t("dashboard.errors.refreshrequired")}
</Typography.Title>
</div> </div>
</Card> </Card>
); );

View File

@@ -198,15 +198,19 @@ const componentList = {
label: "Production Dollars", label: "Production Dollars",
component: DashboardTotalProductionDollars, component: DashboardTotalProductionDollars,
gqlFragment: null, gqlFragment: null,
w: 2, w: 1,
h: 1, h: 1,
minW: 2,
minH: 1,
}, },
ProductionHours: { ProductionHours: {
label: "Production Hours", label: "Production Hours",
component: DashboardTotalProductionHours, component: DashboardTotalProductionHours,
gqlFragment: DashboardTotalProductionHoursGql, gqlFragment: DashboardTotalProductionHoursGql,
w: 2, w: 3,
h: 1, h: 1,
minW: 3,
minH: 1,
}, },
ProjectedMonthlySales: { ProjectedMonthlySales: {
label: "Projected Monthly Sales", label: "Projected Monthly Sales",
@@ -214,6 +218,8 @@ const componentList = {
gqlFragment: DashboardProjectedMonthlySalesGql, gqlFragment: DashboardProjectedMonthlySalesGql,
w: 2, w: 2,
h: 1, h: 1,
minW: 2,
minH: 1,
}, },
MonthlyRevenueGraph: { MonthlyRevenueGraph: {
label: "Monthly Sales Graph", label: "Monthly Sales Graph",
@@ -254,12 +260,15 @@ const componentList = {
}; };
const createDashboardQuery = (state) => { const createDashboardQuery = (state) => {
const componentBasedAdditions = state.layout const componentBasedAdditions =
.map((item, index) => componentList[item.i].gqlFragment || "") state &&
.join(""); Array.isArray(state.layout) &&
state.layout
.map((item, index) => componentList[item.i].gqlFragment || "")
.join("");
return gql` return gql`
query QUERY_DASHBOARD_DETAILS { query QUERY_DASHBOARD_DETAILS {
${componentBasedAdditions} ${componentBasedAdditions || ""}
monthly_sales: jobs(where: {_and: [{date_invoiced: {_gte: "${moment() monthly_sales: jobs(where: {_and: [{date_invoiced: {_gte: "${moment()
.startOf("month") .startOf("month")
.format("YYYY-MM-DD")}"}}, {date_invoiced: {_lte: "${moment() .format("YYYY-MM-DD")}"}}, {date_invoiced: {_lte: "${moment()

View File

@@ -1,4 +1,5 @@
import Icon, { import Icon, {
FileAddOutlined,
BankFilled, BankFilled,
BarChartOutlined, BarChartOutlined,
CarFilled, CarFilled,
@@ -111,12 +112,14 @@ function Header({
{t("menus.header.availablejobs")} {t("menus.header.availablejobs")}
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Item key="newjob" icon={<FileAddOutlined />}>
<Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
</Menu.Item>
<Menu.Divider key="div1" /> <Menu.Divider key="div1" />
<Menu.Item key="alljobs" icon={<UnorderedListOutlined />}> <Menu.Item key="alljobs" icon={<UnorderedListOutlined />}>
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link> <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item> </Menu.Item>
<Menu.Divider key="div2" /> <Menu.Divider key="div2" />
<Menu.Item key="productionlist" icon={<ScheduleOutlined />}> <Menu.Item key="productionlist" icon={<ScheduleOutlined />}>
<Link to="/manage/production/list"> <Link to="/manage/production/list">
{t("menus.header.productionlist")} {t("menus.header.productionlist")}
@@ -128,7 +131,6 @@ function Header({
</Link> </Link>
</Menu.Item> </Menu.Item>
<Menu.Divider key="div3" /> <Menu.Divider key="div3" />
<Menu.Item key="scoreboard" icon={<LineChartOutlined />}> <Menu.Item key="scoreboard" icon={<LineChartOutlined />}>
<Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link> <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
</Menu.Item> </Menu.Item>

View File

@@ -0,0 +1,52 @@
import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function JoblinePresetButton({ bodyshop, form }) {
const { t } = useTranslation();
const handleSelect = (item) => {
form.setFieldsValue(item);
};
const menu = (
<Menu>
{bodyshop.md_jobline_presets.map((i, idx) => (
<Menu.Item onClick={() => handleSelect(i)} onItemHover key={idx}>
{i.label}
</Menu.Item>
))}
</Menu>
);
return (
<div>
<Dropdown trigger={["click"]} overlay={menu}>
<a
className="ant-dropdown-link"
href="# "
onClick={(e) => e.preventDefault()}
>
{t("joblines.labels.presets")} <DownOutlined />
</a>
</Dropdown>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(JoblinePresetButton);

View File

@@ -3,7 +3,7 @@ import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import InputCurrency from "../form-items-formatted/currency-form-item.component"; import InputCurrency 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 JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component";
export default function JobLinesUpsertModalComponent({ export default function JobLinesUpsertModalComponent({
visible, visible,
jobLine, jobLine,
@@ -32,6 +32,7 @@ export default function JobLinesUpsertModalComponent({
onOk={() => form.submit()} onOk={() => form.submit()}
okButtonProps={{ loading: loading }} okButtonProps={{ loading: loading }}
onCancel={handleCancel} onCancel={handleCancel}
e
> >
<Form <Form
onFinish={handleFinish} onFinish={handleFinish}
@@ -41,6 +42,9 @@ export default function JobLinesUpsertModalComponent({
form={form} form={form}
> >
<LayoutFormRow grow> <LayoutFormRow grow>
<Form.Item label={t("joblines.fields.line_no")} name="line_no">
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.line_desc")} label={t("joblines.fields.line_desc")}
rules={[ rules={[
@@ -53,6 +57,7 @@ export default function JobLinesUpsertModalComponent({
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<JoblinesPreset form={form} />
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow>
<Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty"> <Form.Item label={t("joblines.fields.mod_lbr_ty")} name="mod_lbr_ty">

View File

@@ -1,14 +1,14 @@
import { useQuery } from "@apollo/client";
import React, { useContext } from "react"; import React, { useContext } from "react";
import JobsCreateVehicleInfoComponent from "./jobs-create-vehicle-info.component"; import { SEARCH_VEHICLES } from "../../graphql/vehicles.queries";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries"; import JobsCreateVehicleInfoComponent from "./jobs-create-vehicle-info.component";
import { useQuery } from "@apollo/client";
export default function JobsCreateVehicleInfoContainer({ form }) { export default function JobsCreateVehicleInfoContainer({ form }) {
const [state] = useContext(JobCreateContext); const [state] = useContext(JobCreateContext);
const { loading, error, data } = useQuery(SEARCH_VEHICLE_BY_VIN, { const { loading, error, data } = useQuery(SEARCH_VEHICLES, {
variables: { vin: `%${state.vehicle.search}%` }, variables: { search: `%${state.vehicle.search}%` },
skip: !state.vehicle.search, skip: !state.vehicle.search,
}); });
@@ -17,7 +17,7 @@ export default function JobsCreateVehicleInfoContainer({ form }) {
return ( return (
<JobsCreateVehicleInfoComponent <JobsCreateVehicleInfoComponent
loading={loading} loading={loading}
vehicles={data ? data.vehicles : null} vehicles={data ? data.search_vehicles : null}
/> />
); );
} }

View File

@@ -825,6 +825,180 @@ export default function ShopInfoGeneral({ form }) {
}} }}
</Form.List> </Form.List>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")}>
<Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item
label={t("general.labels.label")}
key={`${index}label`}
name={[field.name, "label"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("joblines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
key={`${index}mod_lbr_ty`}
name={[field.name, "mod_lbr_ty"]}
>
<Select allowClear>
<Select.Option value="LAA">
{t("joblines.fields.lbr_types.LAA")}
</Select.Option>
<Select.Option value="LAB">
{t("joblines.fields.lbr_types.LAB")}
</Select.Option>
<Select.Option value="LAD">
{t("joblines.fields.lbr_types.LAD")}
</Select.Option>
<Select.Option value="LAE">
{t("joblines.fields.lbr_types.LAE")}
</Select.Option>
<Select.Option value="LAF">
{t("joblines.fields.lbr_types.LAF")}
</Select.Option>
<Select.Option value="LAG">
{t("joblines.fields.lbr_types.LAG")}
</Select.Option>
<Select.Option value="LAM">
{t("joblines.fields.lbr_types.LAM")}
</Select.Option>
<Select.Option value="LAR">
{t("joblines.fields.lbr_types.LAR")}
</Select.Option>
<Select.Option value="LAS">
{t("joblines.fields.lbr_types.LAS")}
</Select.Option>
<Select.Option value="LAU">
{t("joblines.fields.lbr_types.LAU")}
</Select.Option>
<Select.Option value="LA1">
{t("joblines.fields.lbr_types.LA1")}
</Select.Option>
<Select.Option value="LA2">
{t("joblines.fields.lbr_types.LA2")}
</Select.Option>
<Select.Option value="LA3">
{t("joblines.fields.lbr_types.LA3")}
</Select.Option>
<Select.Option value="LA4">
{t("joblines.fields.lbr_types.LA4")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("joblines.fields.mod_lb_hrs")}
key={`${index}mod_lb_hrs`}
name={[field.name, "mod_lb_hrs"]}
>
<InputNumber precision={1} />
</Form.Item>
<Form.Item
label={t("joblines.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select allowClear>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAE">
{t("joblines.fields.part_types.PAE")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>{" "}
</Form.Item>
<Form.Item
label={t("joblines.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("joblines.fields.part_qty")}
key={`${index}part_qty`}
name={[field.name, "part_qty"]}
>
<CurrencyInput precision={2} min={0} />
</Form.Item>
<Form.Item
label={t("joblines.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput precision={2} min={0} />
</Form.Item>
<Form.Item
label={t("joblines.fields.prt_dsmk_p")}
key={`${index}prt_dsmk_p`}
name={[field.name, "prt_dsmk_p"]}
>
<InputNumber precision={0} min={0} max={100} />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</div> </div>
); );
} }

View File

@@ -228,7 +228,6 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
where: { scheduled_completion: { _gte: $start, _lte: $end } } where: { scheduled_completion: { _gte: $start, _lte: $end } }
) { ) {
id id
ro_number ro_number
scheduled_completion scheduled_completion
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
@@ -250,15 +249,9 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
} }
} }
} }
arrJobs: jobs( arrJobs: jobs(where: { scheduled_in: { _gte: $start, _lte: $end } }) {
where: {
scheduled_in: { _gte: $start, _lte: $end }
removed: { _eq: false }
}
) {
id id
scheduled_in scheduled_in
ro_number ro_number
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }

View File

@@ -88,6 +88,7 @@ export const QUERY_BODYSHOP = gql`
enforce_referral enforce_referral
website website
jc_hourly_rates jc_hourly_rates
md_jobline_presets
employees { employees {
id id
active active
@@ -173,6 +174,7 @@ export const UPDATE_SHOP = gql`
enforce_referral enforce_referral
website website
jc_hourly_rates jc_hourly_rates
md_jobline_presets
employees { employees {
id id
first_name first_name

View File

@@ -159,6 +159,7 @@ export const UPDATE_JOB_LINE = gql`
db_price db_price
act_price act_price
line_desc line_desc
line_no
oem_partno oem_partno
notes notes
location location

View File

@@ -133,6 +133,36 @@ export const SEARCH_VEHICLE_BY_VIN = gql`
} }
`; `;
export const SEARCH_VEHICLES = gql`
query SEARCH_VEHICLES($search: String!) {
search_vehicles(args: { search: $search }) {
id
plate_no
plate_st
v_vin
v_model_yr
v_model_desc
v_make_desc
v_color
v_bstyle
updated_at
v_type
v_trimcode
v_tone
v_stage
v_prod_dt
v_paint_codes
v_options
v_mldgcode
v_makecode
v_engine
v_cond
trim_color
db_v_code
}
}
`;
export const SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE = gql` export const SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE = gql`
query SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) { query SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) {
vehicles_by_pk(id: $id) { vehicles_by_pk(id: $id) {

View File

@@ -57,7 +57,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
useEffect(() => { useEffect(() => {
document.title = t("titles.jobs-create"); document.title = t("titles.jobs-create");
setSelectedHeader("availablejobs"); setSelectedHeader("newjob");
setBreadcrumbs([ setBreadcrumbs([
{ link: "/manage/available", label: t("titles.bc.availablejobs") }, { link: "/manage/available", label: t("titles.bc.availablejobs") },
{ {

View File

@@ -243,6 +243,7 @@
"street2": "Street 2", "street2": "Street 2",
"zip": "Zip/Postal Code" "zip": "Zip/Postal Code"
}, },
"md_jobline_presets": "Jobline Presets",
"md_payment_types": "Payment Types", "md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources", "md_referral_sources": "Referral Sources",
"messaginglabel": "Messaging Preset Label", "messaginglabel": "Messaging Preset Label",
@@ -668,6 +669,7 @@
"addcomponent": "Add Component" "addcomponent": "Add Component"
}, },
"errors": { "errors": {
"refreshrequired": "You must refresh the dashboard data to see this component.",
"updatinglayout": "Error saving updated layout {{message}}" "updatinglayout": "Error saving updated layout {{message}}"
}, },
"labels": { "labels": {
@@ -992,7 +994,8 @@
"billref": "Latest Bill", "billref": "Latest Bill",
"edit": "Edit Line", "edit": "Edit Line",
"new": "New Line", "new": "New Line",
"nostatus": "No Status" "nostatus": "No Status",
"presets": "Jobline Presets"
}, },
"successes": { "successes": {
"created": "Job line created successfully.", "created": "Job line created successfully.",
@@ -1440,6 +1443,7 @@
"help": "Help", "help": "Help",
"home": "Home", "home": "Home",
"jobs": "Jobs", "jobs": "Jobs",
"newjob": "Create New Job",
"owners": "Owners", "owners": "Owners",
"parts-queue": "Parts Queue", "parts-queue": "Parts Queue",
"phonebook": "Phonebook", "phonebook": "Phonebook",

View File

@@ -243,6 +243,7 @@
"street2": "", "street2": "",
"zip": "" "zip": ""
}, },
"md_jobline_presets": "",
"md_payment_types": "", "md_payment_types": "",
"md_referral_sources": "", "md_referral_sources": "",
"messaginglabel": "", "messaginglabel": "",
@@ -668,6 +669,7 @@
"addcomponent": "" "addcomponent": ""
}, },
"errors": { "errors": {
"refreshrequired": "",
"updatinglayout": "" "updatinglayout": ""
}, },
"labels": { "labels": {
@@ -992,7 +994,8 @@
"billref": "", "billref": "",
"edit": "Línea de edición", "edit": "Línea de edición",
"new": "Nueva línea", "new": "Nueva línea",
"nostatus": "" "nostatus": "",
"presets": ""
}, },
"successes": { "successes": {
"created": "", "created": "",
@@ -1440,6 +1443,7 @@
"help": "", "help": "",
"home": "Casa", "home": "Casa",
"jobs": "Trabajos", "jobs": "Trabajos",
"newjob": "",
"owners": "propietarios", "owners": "propietarios",
"parts-queue": "", "parts-queue": "",
"phonebook": "", "phonebook": "",

View File

@@ -243,6 +243,7 @@
"street2": "", "street2": "",
"zip": "" "zip": ""
}, },
"md_jobline_presets": "",
"md_payment_types": "", "md_payment_types": "",
"md_referral_sources": "", "md_referral_sources": "",
"messaginglabel": "", "messaginglabel": "",
@@ -668,6 +669,7 @@
"addcomponent": "" "addcomponent": ""
}, },
"errors": { "errors": {
"refreshrequired": "",
"updatinglayout": "" "updatinglayout": ""
}, },
"labels": { "labels": {
@@ -992,7 +994,8 @@
"billref": "", "billref": "",
"edit": "Ligne d'édition", "edit": "Ligne d'édition",
"new": "Nouvelle ligne", "new": "Nouvelle ligne",
"nostatus": "" "nostatus": "",
"presets": ""
}, },
"successes": { "successes": {
"created": "", "created": "",
@@ -1440,6 +1443,7 @@
"help": "", "help": "",
"home": "Accueil", "home": "Accueil",
"jobs": "Emplois", "jobs": "Emplois",
"newjob": "",
"owners": "Propriétaires", "owners": "Propriétaires",
"parts-queue": "", "parts-queue": "",
"phonebook": "", "phonebook": "",

View File

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

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "md_jobline_presets" jsonb NULL
DEFAULT jsonb_build_array();
type: run_sql

View File

@@ -0,0 +1,84 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- jobsizelimit
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- phone
- prodtargethrs
- production_config
- region_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- stripe_acct_id
- sub_status
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- website
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,85 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- jobsizelimit
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- phone
- prodtargethrs
- production_config
- region_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- stripe_acct_id
- sub_status
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- website
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,78 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- phone
- prodtargethrs
- production_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- target_touchtime
- updated_at
- use_fippa
- website
- workingdays
- zip_post
filter:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -0,0 +1,79 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- phone
- prodtargethrs
- production_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- target_touchtime
- updated_at
- use_fippa
- website
- workingdays
- zip_post
filter:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -779,6 +779,7 @@ tables:
- md_classes - md_classes
- md_hour_split - md_hour_split
- md_ins_cos - md_ins_cos
- md_jobline_presets
- md_labor_rates - md_labor_rates
- md_messaging_presets - md_messaging_presets
- md_notes_presets - md_notes_presets
@@ -849,6 +850,7 @@ tables:
- md_classes - md_classes
- md_hour_split - md_hour_split
- md_ins_cos - md_ins_cos
- md_jobline_presets
- md_labor_rates - md_labor_rates
- md_messaging_presets - md_messaging_presets
- md_notes_presets - md_notes_presets

View File

@@ -31,13 +31,17 @@ exports.default = async (req, res) => {
}, },
}; };
console.log("***Number of Failed jobs***: ", erroredJobs.length); console.log(
"***Number of Failed jobs***: ",
erroredJobs.length,
JSON.stringify(erroredJobs.map((x) => x.error))
);
var ret = builder var ret = builder
.create(autoHouseObject, { .create(autoHouseObject, {
version: "1.0", version: "1.0",
encoding: "UTF-8", encoding: "UTF-8",
}) })
.end({ pretty: true }); .end({ pretty: true, allowEmptyTags: true });
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml //***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
res.type("application/xml"); res.type("application/xml");
@@ -48,6 +52,8 @@ exports.default = async (req, res) => {
const CreateRepairOrderTag = (job, errorCallback) => { const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2 //Level 2
const repairCosts = CreateCosts(job);
try { try {
const ret = { const ret = {
RepairOrderInformation: { RepairOrderInformation: {
@@ -63,8 +69,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
ShopState: job.bodyshop.state, ShopState: job.bodyshop.state,
ShopZip: job.bodyshop.zip_post, ShopZip: job.bodyshop.zip_post,
ShopPhone: job.bodyshop.phone, ShopPhone: job.bodyshop.phone,
EstimatorID: `${job.est_ct_fn} ${job.est_ct_ln}`, EstimatorID: `${job.est_ct_fn || ""} ${job.est_ct_ln || ""}`,
EstimatorName: `${job.est_ct_fn} ${job.est_ct_ln}`, EstimatorName: `${job.est_ct_fn || ""} ${job.est_ct_ln || ""}`,
}, },
CustomerInformation: { CustomerInformation: {
FirstName: job.ownr_fn, FirstName: job.ownr_fn,
@@ -97,7 +103,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
VehiclePaintCode: null, VehiclePaintCode: null,
VehicleTrimCode: null, VehicleTrimCode: null,
VehicleBodyStyle: null, VehicleBodyStyle: null,
DriveableFlag: job.tlos_ind ? "Y" : "N", DriveableFlag: job.driveable ? "Y" : "N",
}, },
InsuranceInformation: { InsuranceInformation: {
@@ -251,25 +257,39 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}, },
RevisedTotals: { RevisedTotals: {
BodyHours: job.job_totals.rates.lab.hours, BodyHours: job.job_totals.rates.lab.hours,
BodyRepairHours: job.joblines
.filter((line) => repairOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
BodyReplaceHours: job.joblines
.filter((line) => replaceOpCodes.includes(line.lbr_op))
.reduce((acc, val) => acc + val.mod_lb_hrs, 0),
RefinishHours: job.job_totals.rates.lar.hours, RefinishHours: job.job_totals.rates.lar.hours,
MechanicalHours: job.job_totals.rates.lam.hours, MechanicalHours: job.job_totals.rates.lam.hours,
StructuralHours: job.job_totals.rates.las.hours, StructuralHours: job.job_totals.rates.las.hours,
PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat( PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(
AHDineroFormat AHDineroFormat
), ),
PartsTotalCost: 0, PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat),
PartsOEM: Dinero( PartsOEM: Dinero(
job.job_totals.parts.parts.list.PAN && job.job_totals.parts.parts.list.PAN &&
job.job_totals.parts.parts.list.PAN.total job.job_totals.parts.parts.list.PAN.total
).toFormat(AHDineroFormat), )
PartsOEMCost: 0, .add(
Dinero(
job.job_totals.parts.parts.list.PAP &&
job.job_totals.parts.parts.list.PAP.total
)
)
.toFormat(AHDineroFormat),
PartsOEMCost: repairCosts.PartsOemCost.toFormat(AHDineroFormat),
PartsAM: Dinero( PartsAM: Dinero(
job.job_totals.parts.parts.list.PAA && job.job_totals.parts.parts.list.PAA &&
job.job_totals.parts.parts.list.PAA.total job.job_totals.parts.parts.list.PAA.total
).toFormat(AHDineroFormat), ).toFormat(AHDineroFormat),
PartsAMCost: 0, PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat),
PartsReconditioned: null, PartsReconditioned: null,
PartsReconditionedCost: null, PartsReconditionedCost:
repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
PartsRecycled: Dinero( PartsRecycled: Dinero(
job.job_totals.parts.parts.list.PAR && job.job_totals.parts.parts.list.PAR &&
job.job_totals.parts.parts.list.PAR.total job.job_totals.parts.parts.list.PAR.total
@@ -389,10 +409,108 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}; };
return ret; return ret;
} catch (error) { } catch (error) {
console.log("Error calculating job", error);
errorCallback(job, error); errorCallback(job, error);
} }
}; };
const CreateCosts = (job) => {
//Create a mapping based on AH Requirements
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
//console.log("JobCostingPartsTable -> line_val", line_val);
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
return null;
});
return bill_acc;
}, {});
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
//If the hourly rates for job costing are set, add them in.
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero();
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount:
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0,
}).multiply(materialsHours.mapaHrs)
);
}
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[
ticket_val.cost_center
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0)
);
return ticket_acc;
},
{}
);
const defaultCosts = job.bodyshop.md_responsibility_centers.defaults.costs;
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
return acc.add(billTotalsByCostCenters[key]);
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
),
PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(),
PartsReconditionedCost: Dinero(),
PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAR] || Dinero(),
PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
SubletTotalCost: billTotalsByCostCenters[defaultCosts.PAS] || Dinero(),
BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
RefinishLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
MechanicalLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
StructuralLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(),
StorageTotalCost: Dinero(),
DetailTotal: Dinero(),
DetailTotalCost: Dinero(),
SalesTaxTotalCost: Dinero(),
};
};
const StatusMapping = (status, md_ro_statuses) => { const StatusMapping = (status, md_ro_statuses) => {
//EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED. //EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED.
const { const {
@@ -493,3 +611,6 @@ const generateNullDetailLine = () => {
EstimateAmount: null, EstimateAmount: null,
}; };
}; };
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];

View File

@@ -338,6 +338,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
rate_mapa rate_mapa
rate_mash rate_mash
job_totals job_totals
driveable
bodyshop { bodyshop {
id id
shopname shopname
@@ -350,6 +351,8 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
md_ro_statuses md_ro_statuses
md_order_statuses md_order_statuses
autohouseid autohouseid
md_responsibility_centers
jc_hourly_rates
} }
joblines (where:{removed: {_eq:false}}){ joblines (where:{removed: {_eq:false}}){
id id
@@ -366,7 +369,10 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
part_qty part_qty
part_type part_type
oem_partno oem_partno
billlines (order_by:{bill:{date:desc_nulls_last}}) { lbr_op
profitcenter_part
profitcenter_labor
billlines (order_by:{bill:{date:desc_nulls_last}}) {
actual_cost actual_cost
actual_price actual_price
quantity quantity
@@ -377,7 +383,27 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
invoice_number invoice_number
} }
} }
}
} bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
id
quantity
}
}
timetickets {
id
rate
cost_center
actualhrs
productivehrs
}
area_of_damage area_of_damage
employee_prep_rel { employee_prep_rel {
first_name first_name

View File

@@ -353,11 +353,12 @@ function CalculateTaxesTotals(job, otherTotals) {
//Audatex sends additional glass part types. IO-774 //Audatex sends additional glass part types. IO-774
const BackupGlassTax = const BackupGlassTax =
job.parts_tax_rates.PAGD || job.parts_tax_rates &&
job.parts_tax_rates.PAGF || (job.parts_tax_rates.PAGD ||
job.parts_tax_rates.PAGP || job.parts_tax_rates.PAGF ||
job.parts_tax_rates.PAGQ || job.parts_tax_rates.PAGP ||
job.parts_tax_rates.PAGR; job.parts_tax_rates.PAGQ ||
job.parts_tax_rates.PAGR);
job.joblines job.joblines
.filter((jl) => !jl.removed) .filter((jl) => !jl.removed)