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

View File

@@ -32,13 +32,9 @@ export default function DashboardMonthlyLaborSales({ data, ...cardProps }) {
return {
name: t(`joblines.fields.lbr_types.${key.toUpperCase()}`),
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 (
<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 RADIAN = Math.PI / 180;
//const RADIAN = Math.PI / 180;
const {
cx,
cy,
midAngle,
//midAngle,
innerRadius,
outerRadius,
startAngle,
endAngle,
fill,
payload,
percent,
// percent,
value,
} = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? "start" : "end";
// const sin = Math.sin(-RADIAN * midAngle);
// const cos = Math.cos(-RADIAN * midAngle);
// // const sx = cx + (outerRadius + 10) * cos;
// const sy = cy + (outerRadius + 10) * sin;
// const mx = cx + (outerRadius + 30) * cos;
// const my = cy + (outerRadius + 30) * sin;
// //const ex = mx + (cos >= 0 ? 1 : -1) * 22;
// const ey = my;
//const textAnchor = cos >= 0 ? "start" : "end";
return (
<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}
</text>
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
</text>
<Sector
cx={cx}
cy={cy}
@@ -120,29 +136,28 @@ const renderActiveShape = (props) => {
outerRadius={outerRadius + 10}
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>
);
};
// <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 {
name: t(`joblines.fields.part_types.${key.toUpperCase()}`),
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 (
<Card title={t("dashboard.titles.monthlypartssales")} {...cardProps}>
@@ -68,37 +64,55 @@ export default function DashboardMonthlyPartsSales({ data, ...cardProps }) {
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 RADIAN = Math.PI / 180;
// const RADIAN = Math.PI / 180;
const {
cx,
cy,
midAngle,
// midAngle,
innerRadius,
outerRadius,
startAngle,
endAngle,
fill,
payload,
percent,
// percent,
value,
} = props;
const sin = Math.sin(-RADIAN * midAngle);
const cos = Math.cos(-RADIAN * midAngle);
const sx = cx + (outerRadius + 10) * cos;
const sy = cy + (outerRadius + 10) * sin;
const mx = cx + (outerRadius + 30) * cos;
const my = cy + (outerRadius + 30) * sin;
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
const ey = my;
const textAnchor = cos >= 0 ? "start" : "end";
// const sin = Math.sin(-RADIAN * midAngle);
// const cos = Math.cos(-RADIAN * midAngle);
// const sx = cx + (outerRadius + 10) * cos;
//const sy = cy + (outerRadius + 10) * sin;
// const mx = cx + (outerRadius + 30) * cos;
//const my = cy + (outerRadius + 30) * sin;
// const ex = mx + (cos >= 0 ? 1 : -1) * 22;
// const ey = my;
// const textAnchor = cos >= 0 ? "start" : "end";
return (
<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}
</text>
<text x={cx} y={cy} dy={16} textAnchor="middle" fill={fill}>
{Dinero({ amount: Math.round(value * 100) }).toFormat()}
</text>
<Sector
cx={cx}
cy={cy}
@@ -117,29 +131,6 @@ const renderActiveShape = (props) => {
outerRadius={outerRadius + 10}
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>
);
};

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
import Icon, {
FileAddOutlined,
BankFilled,
BarChartOutlined,
CarFilled,
@@ -111,12 +112,14 @@ function Header({
{t("menus.header.availablejobs")}
</Link>
</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.Item key="alljobs" icon={<UnorderedListOutlined />}>
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Divider key="div2" />
<Menu.Item key="productionlist" icon={<ScheduleOutlined />}>
<Link to="/manage/production/list">
{t("menus.header.productionlist")}
@@ -128,7 +131,6 @@ function Header({
</Link>
</Menu.Item>
<Menu.Divider key="div3" />
<Menu.Item key="scoreboard" icon={<LineChartOutlined />}>
<Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
</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 InputCurrency from "../form-items-formatted/currency-form-item.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({
visible,
jobLine,
@@ -32,6 +32,7 @@ export default function JobLinesUpsertModalComponent({
onOk={() => form.submit()}
okButtonProps={{ loading: loading }}
onCancel={handleCancel}
e
>
<Form
onFinish={handleFinish}
@@ -41,6 +42,9 @@ export default function JobLinesUpsertModalComponent({
form={form}
>
<LayoutFormRow grow>
<Form.Item label={t("joblines.fields.line_no")} name="line_no">
<InputNumber />
</Form.Item>
<Form.Item
label={t("joblines.fields.line_desc")}
rules={[
@@ -53,6 +57,7 @@ export default function JobLinesUpsertModalComponent({
>
<Input />
</Form.Item>
<JoblinesPreset form={form} />
</LayoutFormRow>
<LayoutFormRow grow>
<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 JobsCreateVehicleInfoComponent from "./jobs-create-vehicle-info.component";
import { SEARCH_VEHICLES } from "../../graphql/vehicles.queries";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import AlertComponent from "../alert/alert.component";
import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { useQuery } from "@apollo/client";
import JobsCreateVehicleInfoComponent from "./jobs-create-vehicle-info.component";
export default function JobsCreateVehicleInfoContainer({ form }) {
const [state] = useContext(JobCreateContext);
const { loading, error, data } = useQuery(SEARCH_VEHICLE_BY_VIN, {
variables: { vin: `%${state.vehicle.search}%` },
const { loading, error, data } = useQuery(SEARCH_VEHICLES, {
variables: { search: `%${state.vehicle.search}%` },
skip: !state.vehicle.search,
});
@@ -17,7 +17,7 @@ export default function JobsCreateVehicleInfoContainer({ form }) {
return (
<JobsCreateVehicleInfoComponent
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>
</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>
);
}

View File

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

View File

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

View File

@@ -159,6 +159,7 @@ export const UPDATE_JOB_LINE = gql`
db_price
act_price
line_desc
line_no
oem_partno
notes
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`
query SEARCH_VEHICLES_BY_ID_FOR_AUTOCOMPLETE($id: uuid!) {
vehicles_by_pk(id: $id) {

View File

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

View File

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

View File

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

View File

@@ -243,6 +243,7 @@
"street2": "",
"zip": ""
},
"md_jobline_presets": "",
"md_payment_types": "",
"md_referral_sources": "",
"messaginglabel": "",
@@ -668,6 +669,7 @@
"addcomponent": ""
},
"errors": {
"refreshrequired": "",
"updatinglayout": ""
},
"labels": {
@@ -992,7 +994,8 @@
"billref": "",
"edit": "Ligne d'édition",
"new": "Nouvelle ligne",
"nostatus": ""
"nostatus": "",
"presets": ""
},
"successes": {
"created": "",
@@ -1440,6 +1443,7 @@
"help": "",
"home": "Accueil",
"jobs": "Emplois",
"newjob": "",
"owners": "Propriétaires",
"parts-queue": "",
"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_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
@@ -849,6 +850,7 @@ tables:
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_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
.create(autoHouseObject, {
version: "1.0",
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
res.type("application/xml");
@@ -48,6 +52,8 @@ exports.default = async (req, res) => {
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
const repairCosts = CreateCosts(job);
try {
const ret = {
RepairOrderInformation: {
@@ -63,8 +69,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
ShopState: job.bodyshop.state,
ShopZip: job.bodyshop.zip_post,
ShopPhone: job.bodyshop.phone,
EstimatorID: `${job.est_ct_fn} ${job.est_ct_ln}`,
EstimatorName: `${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 || ""}`,
},
CustomerInformation: {
FirstName: job.ownr_fn,
@@ -97,7 +103,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
VehiclePaintCode: null,
VehicleTrimCode: null,
VehicleBodyStyle: null,
DriveableFlag: job.tlos_ind ? "Y" : "N",
DriveableFlag: job.driveable ? "Y" : "N",
},
InsuranceInformation: {
@@ -251,25 +257,39 @@ const CreateRepairOrderTag = (job, errorCallback) => {
},
RevisedTotals: {
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,
MechanicalHours: job.job_totals.rates.lam.hours,
StructuralHours: job.job_totals.rates.las.hours,
PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(
AHDineroFormat
),
PartsTotalCost: 0,
PartsTotalCost: repairCosts.PartsTotalCost.toFormat(AHDineroFormat),
PartsOEM: Dinero(
job.job_totals.parts.parts.list.PAN &&
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(
job.job_totals.parts.parts.list.PAA &&
job.job_totals.parts.parts.list.PAA.total
).toFormat(AHDineroFormat),
PartsAMCost: 0,
PartsAMCost: repairCosts.PartsAMCost.toFormat(AHDineroFormat),
PartsReconditioned: null,
PartsReconditionedCost: null,
PartsReconditionedCost:
repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
PartsRecycled: Dinero(
job.job_totals.parts.parts.list.PAR &&
job.job_totals.parts.parts.list.PAR.total
@@ -389,10 +409,108 @@ const CreateRepairOrderTag = (job, errorCallback) => {
};
return ret;
} catch (error) {
console.log("Error calculating 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) => {
//EST, SCH, ARR, IPR, RDY, DEL, CLO, CAN, UNDEFINED.
const {
@@ -493,3 +611,6 @@ const generateNullDetailLine = () => {
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_mash
job_totals
driveable
bodyshop {
id
shopname
@@ -350,6 +351,8 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
md_ro_statuses
md_order_statuses
autohouseid
md_responsibility_centers
jc_hourly_rates
}
joblines (where:{removed: {_eq:false}}){
id
@@ -366,7 +369,10 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
part_qty
part_type
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_price
quantity
@@ -377,7 +383,27 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz) {
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
employee_prep_rel {
first_name

View File

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