Compare commits

...

16 Commits

Author SHA1 Message Date
Patrick Fic
7b3dcf295e IO-2156 Resolve AH detail line validation issue. 2023-01-27 13:20:53 -08:00
Patrick Fic
380dbd8b96 IO-2150 Additional report. 2023-01-27 09:00:44 -08:00
Patrick Fic
a34c2a5bc2 IO-2155 IO-2149 Add report templates. 2023-01-27 08:54:55 -08:00
Patrick Fic
d8ba40979e IO-2154 Add expected jobsin production count. 2023-01-27 08:35:45 -08:00
Patrick Fic
5cd0527e16 Improved global search. 2023-01-27 08:00:54 -08:00
Patrick Fic
25513ae5b5 IO-2151 Resolve issue removing CSR on conversion. 2023-01-26 11:24:53 -08:00
Patrick Fic
d89acbd49d Remove log statement. 2023-01-26 11:12:21 -08:00
Patrick Fic
a54862a309 Add cost center to employee time tickets summary. 2023-01-26 11:07:08 -08:00
Patrick Fic
423157dfcc Add misc labor cost for autohouse. 2023-01-26 08:32:43 -08:00
Patrick Fic
6607c80aca IO-2146 Remove Sublet from jobline status db view. 2023-01-24 09:31:25 -08:00
Patrick Fic
162aeca7c8 Merged in release/2022-01-20 (pull request #657)
Release/2022 01 20
2023-01-20 21:49:59 +00:00
Patrick Fic
1583ed2d61 IO-2143 Add truncation for long vehicle notes. 2023-01-20 13:46:20 -08:00
Patrick Fic
c78b13baa3 Update label for consistency. 2023-01-20 13:46:09 -08:00
Patrick Fic
6528a0c700 Update missed in last commit. 2023-01-19 13:06:43 -08:00
Patrick Fic
79f032ecaf Profile page improvements. 2023-01-19 13:05:00 -08:00
Patrick Fic
42b4534d21 IO-2141 Add detail only line for autohous exports. 2023-01-19 11:04:40 -08:00
31 changed files with 615 additions and 43228 deletions

View File

@@ -736,6 +736,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>expectedjobs</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>expectedprodhrs</name>
<definition_loaded>false</definition_loaded>
@@ -18340,6 +18361,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>ah_detail_line</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>db_price</name>
<definition_loaded>false</definition_loaded>
@@ -19667,6 +19709,27 @@
<folder_node>
<name>validations</name>
<children>
<concept_node>
<name>ahdetailonlyonuserdefinedtypes</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>hrsrequirediflbrtyp</name>
<definition_loaded>false</definition_loaded>
@@ -28933,6 +28996,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>profileadjustments</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>prt_dsmk_total</name>
<definition_loaded>false</definition_loaded>
@@ -41426,6 +41510,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>jobs_completed_not_invoiced</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>jobs_invoiced_not_exported</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>jobs_reconcile</name>
<definition_loaded>false</definition_loaded>
@@ -41552,6 +41678,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>open_orders_specific_csr</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>open_orders_status</name>
<definition_loaded>false</definition_loaded>
@@ -42161,6 +42308,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>scheduled_parts_list</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>scoreboard_detail</name>
<definition_loaded>false</definition_loaded>
@@ -46564,6 +46732,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>changepassword</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>profileinfo</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>

View File

@@ -8,6 +8,8 @@ export default function DataLabel({
vertical,
visible = true,
valueStyle = {},
valueClassName,
onValueClick,
...props
}) {
if (!visible || (hideIfNull && !!!children)) return null;
@@ -28,7 +30,10 @@ export default function DataLabel({
marginLeft: ".3rem",
fontWeight: "bolder",
wordWrap: "break-word",
cursor: onValueClick !== undefined ? "pointer" : "",
}}
className={valueClassName}
onClick={onValueClick}
>
{typeof children === "string" ? (
<Typography.Text style={valueStyle}>{children}</Typography.Text>

View File

@@ -1,6 +1,5 @@
import { useLazyQuery } from "@apollo/client";
import { LoadingOutlined } from "@ant-design/icons";
import { AutoComplete, Divider, Space } from "antd";
import { AutoComplete, Divider, Input, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -9,7 +8,7 @@ import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
OwnerNameDisplayFunction
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() {
@@ -178,13 +177,18 @@ export default function GlobalSearch() {
<AutoComplete
options={options}
onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")}
allowClear
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
></AutoComplete>
>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
);
}

View File

@@ -40,6 +40,11 @@ export function JobLinesUpsertModalComponent({
{},
bodyshop.imexshopid
);
const { Autohouse_Detail_line } = useTreatments(
["Autohouse_Detail_line"],
{},
bodyshop.imexshopid
);
return (
<Modal
@@ -155,6 +160,40 @@ export function JobLinesUpsertModalComponent({
>
<InputNumber precision={1} />
</Form.Item>
{Autohouse_Detail_line.treatment === "on" && (
<Form.Item
label={t("joblines.fields.ah_detail_line")}
name="ah_detail_line"
valuePropName="checked"
dependencies={["mod_lbr_ty"]}
initialValue={false}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (
value === false ||
value === undefined ||
value === null
)
return Promise.resolve();
if (
value === true &&
["LA1", "LA2", "LA3", "LA4", "LAU"].includes(
getFieldValue("mod_lbr_ty")
)
) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.ahdetailonlyonuserdefinedtypes")
);
},
}),
]}
>
<Switch />
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow>
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
@@ -218,7 +257,6 @@ export function JobLinesUpsertModalComponent({
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
console.log(value);
if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve();
}
@@ -229,7 +267,6 @@ export function JobLinesUpsertModalComponent({
}),
({ getFieldValue }) => ({
validator(rule, value) {
console.log(value, !!value);
if (
!!getFieldValue("part_type") === (!!value || value === 0)
) {

View File

@@ -43,14 +43,21 @@ export function JobsConvertButton({
const { t } = useTranslation();
const [form] = Form.useForm();
const handleConvert = async (values) => {
const handleConvert = async ({ employee_csr, ...values }) => {
if (parentFormIsFieldsTouched()) {
alert(t("jobs.labels.savebeforeconversion"));
return;
}
setLoading(true);
const res = await mutationConvertJob({
variables: { jobId: job.id, ...values },
variables: {
jobId: job.id,
job: {
converted: true,
...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}),
...values,
},
},
});
if (values.ca_gst_registrant) {

View File

@@ -5,7 +5,7 @@ import {
BranchesOutlined,
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
import React from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -56,7 +56,7 @@ const colSpan = {
export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation();
const [notesClamped, setNotesClamped] = useState(true);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
@@ -229,6 +229,8 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel
label={t("vehicles.fields.notes")}
valueStyle={{ whiteSpace: "pre-wrap" }}
valueClassName={notesClamped ? "clamp" : ""}
onValueClick={() => setNotesClamped(!notesClamped)}
>
{job.vehicle.notes}
</DataLabel>

View File

@@ -6,3 +6,12 @@
display: flex;
flex-wrap: wrap;
}
.clamp {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
}

View File

@@ -182,7 +182,7 @@ export function JobsExportAllButton({
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
{t("jobs.actions.exportselected")}
</Button>
);
}

View File

@@ -1,4 +1,4 @@
import { Button, Form, Input, notification } from "antd";
import { Button, Card, Col, Form, Input, notification } from "antd";
import { LockOutlined } from "@ant-design/icons";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -48,81 +48,99 @@ export default connect(
};
return (
<div>
<Form
onFinish={handleFinish}
autoComplete={"no"}
initialValues={currentUser}
layout="vertical"
>
<LayoutFormRow>
<Form.Item
label={t("user.fields.displayname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="displayName"
<>
<Col span={24}>
<Form
onFinish={handleFinish}
autoComplete={"no"}
initialValues={currentUser}
layout="vertical"
>
<Card
title={t("user.labels.profileinfo")}
extra={
<Button type="primary" key="submit" htmlType="submit">
{t("user.actions.updateprofile")}
</Button>
}
>
<Input />
</Form.Item>
<Form.Item label={t("user.fields.photourl")} name="photoURL">
<Input />
</Form.Item>
</LayoutFormRow>
<Button type="primary" key="submit" htmlType="submit">
{t("user.actions.updateprofile")}
</Button>
</Form>
<Form
onFinish={handleChangePassword}
autoComplete={"no"}
initialValues={currentUser}
layout="vertical"
>
<LayoutFormRow>
<Form.Item label={t("general.labels.newpassword")} name="password">
<Input
prefix={<LockOutlined />}
type="password"
placeholder={t("general.labels.password")}
/>
</Form.Item>
<Form.Item
label={t("general.labels.confirmpassword")}
name="password-confirm"
dependencies={["password"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
t("general.labels.passwordsdonotmatch")
);
},
}),
]}
<LayoutFormRow noDivider>
<Form.Item
label={t("user.fields.displayname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="displayName"
>
<Input />
</Form.Item>
<Form.Item label={t("user.fields.photourl")} name="photoURL">
<Input />
</Form.Item>
</LayoutFormRow>
</Card>
</Form>
</Col>
<Col span={24}>
<Form
onFinish={handleChangePassword}
autoComplete={"no"}
initialValues={currentUser}
layout="vertical"
>
<Card
title={t("user.labels.changepassword")}
extra={
<Button type="primary" key="submit" htmlType="submit">
{t("user.actions.changepassword")}
</Button>
}
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder={t("general.labels.password")}
/>
</Form.Item>
</LayoutFormRow>
<Button type="primary" key="submit" htmlType="submit">
{t("user.actions.changepassword")}
</Button>
</Form>
</div>
<LayoutFormRow>
<Form.Item
label={t("general.labels.newpassword")}
name="password"
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder={t("general.labels.password")}
/>
</Form.Item>
<Form.Item
label={t("general.labels.confirmpassword")}
name="password-confirm"
dependencies={["password"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
t("general.labels.passwordsdonotmatch")
);
},
}),
]}
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder={t("general.labels.password")}
/>
</Form.Item>
</LayoutFormRow>
</Card>
</Form>
</Col>
</>
);
});

View File

@@ -1,13 +1,13 @@
import React from "react";
import { Button, Card, Col, Input, Table, Typography } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Table, Button, Typography } from "antd";
export default function ProfileShopsComponent({
loading,
data,
updateActiveShop,
}) {
const { t } = useTranslation();
const [search, setSearch] = useState("");
const columns = [
{
title: t("associations.fields.shopname"),
@@ -39,18 +39,39 @@ export default function ProfileShopsComponent({
),
},
];
console.log("🚀 ~ file: profile-shops.component.jsx:45 ~ data", data);
const filteredData =
search === ""
? data
: data.filter((d) =>
d.bodyshop.shopname.toLowerCase().includes(search.toLowerCase())
);
return (
<Table
title={() => (
<Typography.Title level={4}>
{t("profile.labels.activeshop")}
</Typography.Title>
)}
loading={loading}
columns={columns}
rowKey="id"
dataSource={data}
/>
<Col span={24}>
<Card
title={
<Typography.Title level={4}>
{t("profile.labels.activeshop")}
</Typography.Title>
}
extra={
<Input.Search
value={search}
onChange={(e) => setSearch(e.target.value)}
allowClear
placeholder={t("general.labels.search")}
/>
}
>
<Table
pagination={false}
loading={loading}
columns={columns}
rowKey="id"
dataSource={filteredData}
/>
</Card>
</Col>
);
}

View File

@@ -4,11 +4,13 @@ import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import {
Legend, PolarAngleAxis,
Legend,
PolarAngleAxis,
PolarGrid,
PolarRadiusAxis,
Radar, RadarChart,
Tooltip
Radar,
RadarChart,
Tooltip,
} from "recharts";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -20,6 +22,10 @@ const mapDispatchToProps = (dispatch) => ({
});
export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
console.log(
"🚀 ~ file: schedule-calendar-header-graph.component.js:23 ~ ScheduleCalendarHeaderGraph ~ loadData",
loadData
);
const { ssbuckets } = bodyshop;
const { t } = useTranslation();
const data = useMemo(() => {
@@ -44,6 +50,8 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
<Space>
{t("appointments.labels.expectedprodhrs")}
<strong>{loadData?.expectedHours?.toFixed(1)}</strong>
{t("appointments.labels.expectedjobs")}
<strong>{loadData?.expectedJobCount}</strong>
</Space>
<RadarChart
// cx={300}

View File

@@ -1344,7 +1344,14 @@ export default function ShopInfoGeneral({ form }) {
>
<InputNumber precision={0} min={0} max={100} />
</Form.Item>
<Form.Item
label={t("joblines.fields.ah_detail_line")}
key={`${index}ah_detail_line`}
name={[field.name, "ah_detail_line"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {

View File

@@ -85,7 +85,7 @@ export function TimeTicketList({
text: (() => {
const emp = bodyshop.employees.find((e) => e.id === s);
return `${emp.first_name} ${emp.last_name}`;
return `${emp?.first_name} ${emp?.last_name}`;
})(), //
value: [s],
};

View File

@@ -131,6 +131,7 @@ const JobRelatedTicketsTable = ({
return {
id: `${item.jobKey}${costCenter}`,
costCenter,
item,
actHrs: actHrs.toFixed(1),
prodHrs: prodHrs.toFixed(1),
@@ -151,7 +152,9 @@ const JobRelatedTicketsTable = ({
sortOrder:
state.sortedInfo.columnKey === "empname" && state.sortedInfo.order,
render: (text, record) =>
`${record.item.employee.first_name} ${record.item.employee.last_name}`,
`${record.item.employee.first_name} ${record.item.employee.last_name} ${
record.costCenter ? `(${record.costCenter})` : ""
}`.trim(),
},
{
title: t("timetickets.fields.actualhrs"),

View File

@@ -720,6 +720,7 @@ export const GET_JOB_BY_PK = gql`
prt_dsmk_m
ioucreated
convertedtolbr
ah_detail_line
billlines(limit: 1, order_by: { bill: { date: desc } }) {
id
quantity
@@ -1152,31 +1153,8 @@ export const UPDATE_JOBS = gql`
`;
export const CONVERT_JOB_TO_RO = gql`
mutation CONVERT_JOB_TO_RO(
$jobId: uuid!
$class: String
$ins_co_nm: String!
$ca_gst_registrant: Boolean
$driveable: Boolean
$towin: Boolean
$referral_source: String
$referral_source_extra: String
$employee_csr: uuid
) {
update_jobs(
where: { id: { _eq: $jobId } }
_set: {
converted: true
ins_co_nm: $ins_co_nm
class: $class
ca_gst_registrant: $ca_gst_registrant
towin: $towin
driveable: $driveable
referral_source: $referral_source
referral_source_extra: $referral_source_extra
employee_csr: $employee_csr
}
) {
mutation CONVERT_JOB_TO_RO($jobId: uuid!, $job: jobs_set_input!) {
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
returning {
id
ro_number

View File

@@ -1,3 +1,4 @@
import { Row } from "antd";
import React from "react";
import ProfileMyComponent from "../../components/profile-my/profile-my.component";
import ProfileShopsContainer from "../../components/profile-shops/profile-shops.container";
@@ -5,8 +6,10 @@ import ProfileShopsContainer from "../../components/profile-shops/profile-shops.
export default function ProfilePage() {
return (
<div>
<ProfileMyComponent />
<ProfileShopsContainer />
<Row gutter={[16, 16]}>
<ProfileMyComponent />
<ProfileShopsContainer />
</Row>
</div>
);
}

View File

@@ -154,6 +154,10 @@ export function* calculateScheduleLoad({ payload: end }) {
load[current].jobsIn || [],
load[current].jobsOut || []
);
load[current].expectedJobCount =
prodJobs.length +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours =
load.productionHours +
(load[current].hoursIn || 0) -
@@ -165,6 +169,10 @@ export function* calculateScheduleLoad({ payload: end }) {
load[current].jobsIn || [],
load[current].jobsOut || []
);
load[current].expectedJobCount =
load[prev].expectedJobCount +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours =
load[prev].expectedHours +
(load[current].hoursIn || 0) -

View File

@@ -50,6 +50,7 @@
"cancelledappointment": "Canceled appointment for: ",
"completingjobs": "Completing Jobs",
"dataconsistency": "{{ro_number}} has a data consistency issue. It has been excluded for scheduling purposes. CODE: {{code}}.",
"expectedjobs": "Expected Jobs in Production: ",
"expectedprodhrs": "Expected Production Hours:",
"history": "History",
"inproduction": "Jobs In Production",
@@ -1142,6 +1143,7 @@
},
"fields": {
"act_price": "Retail Price",
"ah_detail_line": "Mark as Detail Labor Line (Autohouse Only)",
"db_price": "List Price",
"lbr_types": {
"LA1": "LA1",
@@ -1214,6 +1216,7 @@
"updated": "Job line updated successfully."
},
"validations": {
"ahdetailonlyonuserdefinedtypes": "Detail line indicator can only be set for LA1, LA2, LA3, LA4, and LAU labor types.",
"hrsrequirediflbrtyp": "Labor hours are required if a labor type is selected. Clear the labor type if there are no labor hours.",
"requiredifparttype": "Required if a part type has been specified.",
"zeropriceexistingpart": "This line cannot have any price since it uses an existing part."
@@ -1694,6 +1697,7 @@
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
"totalreturns": "The total <b>retail</b> amount of returns created for this job."
},
"profileadjustments": "",
"prt_dsmk_total": "Line Item Adjustment",
"rates": "Rates",
"rates_subtotal": "All Rates Subtotal",
@@ -2451,12 +2455,15 @@
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
"job_costing_ro_estimator": "Job Costing by Estimator",
"job_costing_ro_ins_co": "Job Costing by RO Source",
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
"lag_time": "Lag Time",
"open_orders": "Open Orders by Date",
"open_orders_csr": "Open Orders by CSR",
"open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company",
"open_orders_specific_csr": "Open Orders filtered by CSR",
"open_orders_status": "Open Orders by Status",
"parts_backorder": "IOU Parts List",
"parts_not_recieved": "Parts Not Received",
@@ -2486,6 +2493,7 @@
"returns_grouped_by_vendor_detailed": "Returns Grouped by Vendor - Detailed",
"returns_grouped_by_vendor_summary": "Returns Grouped by Vendor - Summary",
"schedule": "Appointment Schedule",
"scheduled_parts_list": "Parts for Jobs Scheduled In",
"scoreboard_detail": "Scoreboard Detail",
"scoreboard_summary": "Scoreboard Summary",
"supplement_ratio_ins_co": "Supplement Ratio by Source",
@@ -2744,7 +2752,9 @@
"photourl": "Avatar URL"
},
"labels": {
"actions": "Actions"
"actions": "Actions",
"changepassword": "Change Password",
"profileinfo": "Profile Info"
},
"successess": {
"passwordchanged": "Password changed successfully. "

View File

@@ -50,6 +50,7 @@
"cancelledappointment": "Cita cancelada para:",
"completingjobs": "",
"dataconsistency": "",
"expectedjobs": "",
"expectedprodhrs": "",
"history": "",
"inproduction": "",
@@ -1142,6 +1143,7 @@
},
"fields": {
"act_price": "Precio actual",
"ah_detail_line": "",
"db_price": "Precio de base de datos",
"lbr_types": {
"LA1": "",
@@ -1214,6 +1216,7 @@
"updated": ""
},
"validations": {
"ahdetailonlyonuserdefinedtypes": "",
"hrsrequirediflbrtyp": "",
"requiredifparttype": "",
"zeropriceexistingpart": ""
@@ -1694,6 +1697,7 @@
"partstotal": "",
"totalreturns": ""
},
"profileadjustments": "",
"prt_dsmk_total": "",
"rates": "Tarifas",
"rates_subtotal": "",
@@ -2451,12 +2455,15 @@
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "",
"jobs_completed_not_invoiced": "",
"jobs_invoiced_not_exported": "",
"jobs_reconcile": "",
"lag_time": "",
"open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"open_orders_specific_csr": "",
"open_orders_status": "",
"parts_backorder": "",
"parts_not_recieved": "",
@@ -2486,6 +2493,7 @@
"returns_grouped_by_vendor_detailed": "",
"returns_grouped_by_vendor_summary": "",
"schedule": "",
"scheduled_parts_list": "",
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "",
@@ -2744,7 +2752,9 @@
"photourl": "URL de avatar"
},
"labels": {
"actions": ""
"actions": "",
"changepassword": "",
"profileinfo": ""
},
"successess": {
"passwordchanged": ""

View File

@@ -50,6 +50,7 @@
"cancelledappointment": "Rendez-vous annulé pour:",
"completingjobs": "",
"dataconsistency": "",
"expectedjobs": "",
"expectedprodhrs": "",
"history": "",
"inproduction": "",
@@ -1142,6 +1143,7 @@
},
"fields": {
"act_price": "Prix actuel",
"ah_detail_line": "",
"db_price": "Prix de la base de données",
"lbr_types": {
"LA1": "",
@@ -1214,6 +1216,7 @@
"updated": ""
},
"validations": {
"ahdetailonlyonuserdefinedtypes": "",
"hrsrequirediflbrtyp": "",
"requiredifparttype": "",
"zeropriceexistingpart": ""
@@ -1694,6 +1697,7 @@
"partstotal": "",
"totalreturns": ""
},
"profileadjustments": "",
"prt_dsmk_total": "",
"rates": "Les taux",
"rates_subtotal": "",
@@ -2451,12 +2455,15 @@
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "",
"jobs_completed_not_invoiced": "",
"jobs_invoiced_not_exported": "",
"jobs_reconcile": "",
"lag_time": "",
"open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"open_orders_specific_csr": "",
"open_orders_status": "",
"parts_backorder": "",
"parts_not_recieved": "",
@@ -2486,6 +2493,7 @@
"returns_grouped_by_vendor_detailed": "",
"returns_grouped_by_vendor_summary": "",
"schedule": "",
"scheduled_parts_list": "",
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "",
@@ -2744,7 +2752,9 @@
"photourl": "URL de l'avatar"
},
"labels": {
"actions": ""
"actions": "",
"changepassword": "",
"profileinfo": ""
},
"successess": {
"passwordchanged": ""

View File

@@ -1421,6 +1421,19 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
open_orders_specific_csr: {
title: i18n.t("reportcenter.templates.open_orders_specific_csr"),
description: "",
subject: i18n.t("reportcenter.templates.open_orders_specific_csr"),
key: "open_orders_specific_csr",
idtype: "employee",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "jobs",
},
export_payables: {
title: i18n.t("reportcenter.templates.export_payables"),
description: "",
@@ -1734,6 +1747,46 @@ export const TemplateList = (type, context) => {
},
group: "jobs",
},
scheduled_parts_list: {
title: i18n.t("reportcenter.templates.scheduled_parts_list"),
subject: i18n.t("reportcenter.templates.scheduled_parts_list"),
key: "scheduled_parts_list",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.scheduled_in"),
},
group: "jobs",
},
jobs_completed_not_invoiced: {
title: i18n.t("reportcenter.templates.jobs_completed_not_invoiced"),
subject: i18n.t(
"reportcenter.templates.jobs_completed_not_invoiced"
),
key: "jobs_completed_not_invoiced",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "jobs",
},
jobs_invoiced_not_exported: {
title: i18n.t("reportcenter.templates.jobs_invoiced_not_exported"),
subject: i18n.t(
"reportcenter.templates.jobs_invoiced_not_exported"
),
key: "jobs_invoiced_not_exported",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "jobs",
},
}
: {}),
...(!type || type === "courtesycarcontract"

View File

@@ -2305,18 +2305,6 @@
shallowequal "^1.1.0"
unfetch "^4.2.0"
"@stripe/react-stripe-js@^1.9.0":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.9.0.tgz#74809a274d7db110c3daf6f68ca4d62c6e6559c7"
integrity sha512-Fn49X+Gu5fOTxhPOita1cPMi0jw+0v4xfJ3FCXbbvmfuuDl3M7ZvpRkoijBjql11NXsaXO3TMm3rkN3mEolJzw==
dependencies:
prop-types "^15.7.2"
"@stripe/stripe-js@^1.32.0":
version "1.32.0"
resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.32.0.tgz#4ecdd298db61ad9b240622eafed58da974bd210e"
integrity sha512-7EvBnbBfS1aynfLRmBFcuumHNGjKxnNkO47rorFBktqDYHwo7Yw6pfDW2iqq0R8r7i7XiJEdWPvvEgQAiDrx3A==
"@surma/rollup-plugin-off-main-thread@^1.1.1":
version "1.4.2"
resolved "https://registry.yarnpkg.com/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-1.4.2.tgz#e6786b6af5799f82f7ab3a82e53f6182d2b91a58"

View File

@@ -2482,6 +2482,7 @@
_eq: true
columns:
- act_price
- ah_detail_line
- alt_co_id
- alt_overrd
- alt_part_i
@@ -2547,6 +2548,7 @@
permission:
columns:
- act_price
- ah_detail_line
- alt_co_id
- alt_overrd
- alt_part_i
@@ -2623,6 +2625,7 @@
permission:
columns:
- act_price
- ah_detail_line
- alt_co_id
- alt_overrd
- alt_part_i

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."joblines" add column "ah_detail_line" boolean
-- not null default 'false';

View File

@@ -0,0 +1,2 @@
alter table "public"."joblines" add column "ah_detail_line" boolean
not null default 'false';

View File

@@ -0,0 +1,10 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
-- SELECT j.jobid,
-- j.status,
-- count(1) AS count,
-- j.part_type
-- FROM joblines j
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.removed IS FALSE))
-- GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE VIEW "public"."joblines_status" AS
SELECT j.jobid,
j.status,
count(1) AS count,
j.part_type
FROM joblines j
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.removed IS FALSE))
GROUP BY j.jobid, j.status, j.part_type;

File diff suppressed because one or more lines are too long

View File

@@ -7,7 +7,7 @@
"npm": ">=8.0.0"
},
"scripts": {
"setup": "yarn && cd client && yarn",
"setup": "rm -rf node_modules && yarn && cd client && rm -rf node_modules && yarn",
"admin": "cd admin && yarn start",
"client": "cd client && yarn start",
"server": "nodemon server.js",

View File

@@ -214,6 +214,30 @@ const CreateRepairOrderTag = (job, errorCallback) => {
const repairCosts = CreateCosts(job);
if (job.ro_number === "QBD209") {
console.log("Stop here");
}
//Calculate detail only lines.
const detailAdjustments = job.joblines
.filter((jl) => jl.ah_detail_line && jl.mod_lbr_ty)
.reduce(
(acc, val) => {
return {
hours: acc.hours + val.mod_lb_hrs,
amount: acc.amount.add(
Dinero({
amount: Math.round(
(job.job_totals.rates[val.mod_lbr_ty.toLowerCase()].rate || 0) *
val.mod_lb_hrs *
100
),
})
),
};
},
{ hours: 0, amount: Dinero() }
);
try {
const ret = {
RepairOrderInformation: {
@@ -405,7 +429,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
ElectricalRate: job.rate_lae || 0,
FrameRate: job.rate_laf || 0,
GlassRate: job.rate_lag || 0,
DetailRate: job.rate_lad || 0,
DetailRate: 0, // job.rate_lad || 0,
LaborMiscRate: 0,
PMRate: job.rate_mapa || 0,
BMRate: job.rate_mash || 0,
@@ -500,13 +524,14 @@ const CreateRepairOrderTag = (job, errorCallback) => {
ElectricalHours: job.job_totals.rates.lae.hours.toFixed(2),
FrameHours: job.job_totals.rates.laf.hours.toFixed(2),
GlassHours: job.job_totals.rates.lag.hours.toFixed(2),
DetailHours: job.job_totals.rates.lad.hours.toFixed(2),
DetailHours: detailAdjustments.hours, //job.job_totals.rates.lad.hours.toFixed(2),
LaborMiscHours: (
job.job_totals.rates.la1.hours +
job.job_totals.rates.la2.hours +
job.job_totals.rates.la3.hours +
job.job_totals.rates.la4.hours +
job.job_totals.rates.lau.hours
job.job_totals.rates.lau.hours -
detailAdjustments.hours
).toFixed(2),
PartsTotal: Dinero(job.job_totals.parts.parts.total).toFormat(
@@ -585,16 +610,18 @@ const CreateRepairOrderTag = (job, errorCallback) => {
),
GlassLaborTotalCost:
repairCosts.GlassLaborTotalCost.toFormat(AHDineroFormat),
DetailLaborTotal: Dinero(job.job_totals.rates.lad.total).toFormat(
AHDineroFormat
),
DetailLaborTotalCost:
repairCosts.DetailLaborTotalCost.toFormat(AHDineroFormat),
DetailLaborTotal: detailAdjustments.amount.toFormat(AHDineroFormat),
// Dinero(job.job_totals.rates.lad.total).toFormat(
// AHDineroFormat
// ),
DetailLaborTotalCost: Dinero().toFormat(AHDineroFormat),
// repairCosts.DetailLaborTotalCost.toFormat(AHDineroFormat),
LaborMiscTotal: Dinero(job.job_totals.rates.la1.total)
.add(Dinero(job.job_totals.rates.la2.total))
.add(Dinero(job.job_totals.rates.la3.total))
.add(Dinero(job.job_totals.rates.la4.total))
.add(Dinero(job.job_totals.rates.lau.total))
.subtract(detailAdjustments.amount)
.toFormat(AHDineroFormat),
LaborMiscTotalCost: 0,
MiscellaneousChargeTotal: 0,
@@ -619,7 +646,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AHDineroFormat
),
StorageTotalCost: repairCosts.StorageTotalCost.toFormat(AHDineroFormat),
DetailTotal: 0,
DetailTotal: detailAdjustments.amount.toFormat(AHDineroFormat),
DetailTotalCost: 0,
SalesTaxTotal: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
@@ -866,8 +893,14 @@ const CreateCosts = (job) => {
ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
FrameLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
DetailLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
DetailLaborTotalCost: Dinero(),
// ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
LaborMiscTotalCost: (ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()),
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),

View File

@@ -715,6 +715,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
lbr_op
profitcenter_part
profitcenter_labor
ah_detail_line
parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){
parts_order{
id