Compare commits
27 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
739265ee6a | ||
|
|
038aaf249e | ||
|
|
e0eb4657d2 | ||
|
|
02a6ccd481 | ||
|
|
79e75a5e73 | ||
|
|
8d22248f4b | ||
|
|
bb8024ba9c | ||
|
|
a960963e36 | ||
|
|
3be50b5067 | ||
|
|
563c1d2402 | ||
|
|
2108a4e96c | ||
|
|
c04a690dc3 | ||
|
|
20e84668a5 | ||
|
|
2e40583d31 | ||
|
|
07c307e17b | ||
|
|
176774a888 | ||
|
|
7b3dcf295e | ||
|
|
380dbd8b96 | ||
|
|
a34c2a5bc2 | ||
|
|
d8ba40979e | ||
|
|
5cd0527e16 | ||
|
|
25513ae5b5 | ||
|
|
d89acbd49d | ||
|
|
a54862a309 | ||
|
|
423157dfcc | ||
|
|
6607c80aca | ||
|
|
162aeca7c8 |
@@ -1,4 +1,4 @@
|
||||
<babeledit_project version="1.2" be_version="2.7.1">
|
||||
<babeledit_project be_version="2.7.1" version="1.2">
|
||||
<!--
|
||||
|
||||
BabelEdit project 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>
|
||||
@@ -946,6 +967,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>severalerrorsfound</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>smartscheduling</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -40859,6 +40901,69 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>gsr_by_atp</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>gsr_by_ats</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>gsr_by_category</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>gsr_by_csr</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -41489,6 +41594,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>
|
||||
@@ -41615,6 +41762,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>
|
||||
@@ -42224,6 +42392,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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,9 +22,20 @@ export function JoblinePresetButton({ bodyshop, form }) {
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<Menu>
|
||||
<Menu
|
||||
style={{
|
||||
columnCount: Math.max(
|
||||
Math.floor(bodyshop.md_jobline_presets.length / 15),
|
||||
1
|
||||
),
|
||||
}}
|
||||
>
|
||||
{bodyshop.md_jobline_presets.map((i, idx) => (
|
||||
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
|
||||
<Menu.Item
|
||||
onClick={() => handleSelect(i)}
|
||||
key={idx}
|
||||
style={{ breakInside: "avoid" }}
|
||||
>
|
||||
{i.label}
|
||||
</Menu.Item>
|
||||
))}
|
||||
|
||||
@@ -166,16 +166,16 @@ export function JobLinesUpsertModalComponent({
|
||||
name="ah_detail_line"
|
||||
valuePropName="checked"
|
||||
dependencies={["mod_lbr_ty"]}
|
||||
initialValue={false}
|
||||
rules={[
|
||||
({ getFieldValue }) => ({
|
||||
validator(rule, value) {
|
||||
console.log(
|
||||
value === true,
|
||||
["LA1", "LA2", "LA3", "LA4", "LAU"].includes(
|
||||
getFieldValue("mod_lbr_ty")
|
||||
)
|
||||
);
|
||||
if (value === false) return Promise.resolve();
|
||||
if (
|
||||
value === false ||
|
||||
value === undefined ||
|
||||
value === null
|
||||
)
|
||||
return Promise.resolve();
|
||||
if (
|
||||
value === true &&
|
||||
["LA1", "LA2", "LA3", "LA4", "LAU"].includes(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -34,7 +34,9 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
render: (text, record) =>
|
||||
record.vehicleid ? (
|
||||
<Link to={`/manage/vehicles/${record.vehicleid}`}>
|
||||
{`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc}`}
|
||||
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
|
||||
record.v_model_desc || ""
|
||||
}`.trim()}
|
||||
</Link>
|
||||
) : (
|
||||
t("jobs.errors.novehicle")
|
||||
|
||||
@@ -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";
|
||||
@@ -44,6 +46,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}
|
||||
|
||||
@@ -66,8 +66,8 @@ export function ScheduleCalendarHeaderComponent({
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<table>
|
||||
<tbody>
|
||||
{loadData && loadData.jobsOut ? (
|
||||
loadData.jobsOut.map((j) => (
|
||||
{loadData && loadData.allJobsOut ? (
|
||||
loadData.allJobsOut.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
@@ -102,11 +102,12 @@ export function ScheduleCalendarHeaderComponent({
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<table>
|
||||
<tbody>
|
||||
{loadData && loadData.jobsIn ? (
|
||||
loadData.jobsIn.map((j) => (
|
||||
{loadData && loadData.allJobsIn ? (
|
||||
loadData.allJobsIn.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
{j.status}
|
||||
</td>
|
||||
<td>
|
||||
<OwnerNameDisplay ownerObject={j} />
|
||||
@@ -142,7 +143,7 @@ export function ScheduleCalendarHeaderComponent({
|
||||
title={t("appointments.labels.arrivingjobs")}
|
||||
>
|
||||
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
||||
{(loadData.hoursIn || 0) && loadData.hoursIn.toFixed(2)}
|
||||
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
|
||||
</Popover>
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
@@ -151,7 +152,7 @@ export function ScheduleCalendarHeaderComponent({
|
||||
title={t("appointments.labels.completingjobs")}
|
||||
>
|
||||
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
||||
{(loadData.hoursOut || 0) && loadData.hoursOut.toFixed(2)}
|
||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
|
||||
</Popover>
|
||||
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ import HeaderComponent from "./schedule-calendar-header.component";
|
||||
import "./schedule-calendar.styles.scss";
|
||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||
import { Alert } from "antd";
|
||||
import { Alert, Collapse } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -53,7 +53,28 @@ export function ScheduleCalendarWrapperComponent({
|
||||
return (
|
||||
<>
|
||||
<JobDetailCards />
|
||||
{problemJobs &&
|
||||
{problemJobs && problemJobs.length > 2 ? (
|
||||
<Collapse>
|
||||
<Collapse.Panel
|
||||
header={
|
||||
<span style={{ color: "tomato" }}>
|
||||
{t("appointments.labels.severalerrorsfound")}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{problemJobs.map((problem) => (
|
||||
<Alert
|
||||
key={problem.id}
|
||||
type="error"
|
||||
message={t("appointments.labels.dataconsistency", {
|
||||
ro_number: problem.ro_number,
|
||||
code: problem.code,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
) : (
|
||||
problemJobs.map((problem) => (
|
||||
<Alert
|
||||
key={problem.id}
|
||||
@@ -63,7 +84,8 @@ export function ScheduleCalendarWrapperComponent({
|
||||
code: problem.code,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
|
||||
<Calendar
|
||||
events={data}
|
||||
|
||||
@@ -15,6 +15,7 @@ import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { calculateScheduleLoad } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||
@@ -28,6 +29,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)),
|
||||
});
|
||||
|
||||
export function ScheduleJobModalComponent({
|
||||
@@ -36,6 +38,7 @@ export function ScheduleJobModalComponent({
|
||||
existingAppointments,
|
||||
lbrHrsData,
|
||||
jobId,
|
||||
calculateScheduleLoad,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -57,6 +60,7 @@ export function ScheduleJobModalComponent({
|
||||
|
||||
const handleDateBlur = () => {
|
||||
const values = form.getFieldsValue();
|
||||
|
||||
if (lbrHrsData) {
|
||||
const totalHours =
|
||||
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
@@ -130,7 +134,12 @@ export function ScheduleJobModalComponent({
|
||||
className="imex-flex-row__margin"
|
||||
key={idx}
|
||||
onClick={() => {
|
||||
form.setFieldsValue({ start: new moment(d).add(8, "hours") });
|
||||
const ssDate = moment(d);
|
||||
if (ssDate.isBefore(moment())) {
|
||||
form.setFieldsValue({ start: moment() });
|
||||
} else {
|
||||
form.setFieldsValue({ start: moment(d).add(8, "hours") });
|
||||
}
|
||||
handleDateBlur();
|
||||
}}
|
||||
>
|
||||
@@ -191,6 +200,9 @@ export function ScheduleJobModalComponent({
|
||||
<Form.Item shouldUpdate={(prev, cur) => prev.start !== cur.start}>
|
||||
{() => {
|
||||
const values = form.getFieldsValue();
|
||||
if (values.start) {
|
||||
calculateScheduleLoad(moment(values.start).add(3, "days"));
|
||||
}
|
||||
return (
|
||||
<div className="schedule-job-modal">
|
||||
<ScheduleDayViewContainer day={values.start} />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.schedule-job-modal {
|
||||
height: 70vh;
|
||||
overflow-y: auto;
|
||||
.rbc-calendar {
|
||||
.rbc-toolbar {
|
||||
.rbc-btn-group {
|
||||
|
||||
@@ -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],
|
||||
};
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -294,6 +294,12 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
||||
where: { inproduction: { _eq: true }, suspended: { _eq: false } }
|
||||
) {
|
||||
id
|
||||
actual_in
|
||||
scheduled_in
|
||||
actual_completion
|
||||
scheduled_completion
|
||||
inproduction
|
||||
ro_number
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
@@ -327,12 +333,15 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
||||
}
|
||||
) {
|
||||
id
|
||||
status
|
||||
ro_number
|
||||
scheduled_completion
|
||||
actual_completion
|
||||
scheduled_in
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
inproduction
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
@@ -360,11 +369,16 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
||||
) {
|
||||
id
|
||||
scheduled_in
|
||||
actual_in
|
||||
scheduled_completion
|
||||
ro_number
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
alt_transport
|
||||
actual_completion
|
||||
inproduction
|
||||
status
|
||||
labhrs: joblines_aggregate(
|
||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||
) {
|
||||
|
||||
@@ -1153,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
|
||||
|
||||
@@ -39,9 +39,11 @@ export function VehicleDetailContainer({
|
||||
document.title = t("titles.vehicledetail", {
|
||||
vehicle:
|
||||
data && data.vehicles_by_pk
|
||||
? `${data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr} ${
|
||||
data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc
|
||||
} ${data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc}`
|
||||
? `${(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""} ${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
|
||||
} ${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) || ""
|
||||
}`
|
||||
: "",
|
||||
});
|
||||
setSelectedHeader("vehicles");
|
||||
@@ -53,7 +55,14 @@ export function VehicleDetailContainer({
|
||||
label: t("titles.bc.vehicle-details", {
|
||||
vehicle:
|
||||
data && data.vehicles_by_pk
|
||||
? `${data.vehicles_by_pk.v_model_yr} ${data.vehicles_by_pk.v_make_desc} ${data.vehicles_by_pk.v_model_desc}`
|
||||
? `${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_yr) || ""
|
||||
} ${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_make_desc) || ""
|
||||
} ${
|
||||
(data.vehicles_by_pk && data.vehicles_by_pk.v_model_desc) ||
|
||||
""
|
||||
}`
|
||||
: "",
|
||||
}),
|
||||
},
|
||||
@@ -64,7 +73,11 @@ export function VehicleDetailContainer({
|
||||
CreateRecentItem(
|
||||
vehId,
|
||||
"vehicle",
|
||||
`${data.vehicles_by_pk.v_vin} | ${data.vehicles_by_pk.v_model_yr} ${data.vehicles_by_pk.v_make_desc} ${data.vehicles_by_pk.v_model_desc}`,
|
||||
`${data.vehicles_by_pk.v_vin || "N/A"} | ${
|
||||
data.vehicles_by_pk.v_model_yr || ""
|
||||
} ${data.vehicles_by_pk.v_make_desc || ""} ${
|
||||
data.vehicles_by_pk.v_model_desc || ""
|
||||
}`.trim(),
|
||||
`/manage/vehicles/${vehId}`
|
||||
)
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
productionTotal: {},
|
||||
productionHours: 0,
|
||||
};
|
||||
|
||||
let problemJobs = [];
|
||||
//Set the current load.
|
||||
buckets.forEach((bucket) => {
|
||||
load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
|
||||
@@ -45,6 +45,32 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
|
||||
prodJobs.forEach((item) => {
|
||||
//Add all of the jobs currently in production to the buckets so that we have a starting point.
|
||||
if (
|
||||
!item.actual_completion &&
|
||||
moment(item.scheduled_completion).isBefore(moment().startOf("day"))
|
||||
) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job was scheduled to go, but it has not been completed. Update the scheduled completion date to correct projections",
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
item.actual_completion &&
|
||||
moment(item.actual_completion).isBefore(moment().startOf("day"))
|
||||
) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job is already marked as completed, but it is still in production. This job should be removed from production",
|
||||
});
|
||||
}
|
||||
if (!(item.actual_completion || item.scheduled_completion)) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job does not have a scheduled or actual completion date. Update the scheduled or actual completion dates to correct projections",
|
||||
});
|
||||
}
|
||||
|
||||
const bucketId = CheckJobBucket(buckets, item);
|
||||
load.productionHours =
|
||||
load.productionHours +
|
||||
@@ -59,77 +85,120 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
});
|
||||
|
||||
arrJobs.forEach((item) => {
|
||||
if (!item.scheduled_in)
|
||||
if (!item.scheduled_in) {
|
||||
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
|
||||
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job has no scheduled in date",
|
||||
});
|
||||
}
|
||||
if (!item.actual_completion && item.actual_in && !item.inproduction) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job has an actual in date, but no actual completion date and is not marked as in production",
|
||||
});
|
||||
}
|
||||
if (item.actual_in && moment(item.actual_in).isAfter(moment())) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job has an actual in date set in the future",
|
||||
});
|
||||
}
|
||||
if (
|
||||
item.actual_completion &&
|
||||
moment(item.actual_completion).isAfter(moment())
|
||||
) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job has an actual completion date set in the future",
|
||||
});
|
||||
}
|
||||
if (item.actual_completion && item.inproduction) {
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job has an actual completion date but it is still marked in production",
|
||||
});
|
||||
}
|
||||
|
||||
const itemDate = moment(item.actual_in || item.scheduled_in).format(
|
||||
"yyyy-MM-DD"
|
||||
);
|
||||
|
||||
const AddJobForSchedulingCalc = !item.inproduction;
|
||||
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursIn =
|
||||
(load[itemDate].hoursIn || 0) +
|
||||
load[itemDate].allHoursIn =
|
||||
(load[itemDate].allHoursIn || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].jobsIn.push(item);
|
||||
|
||||
//If the job hasn't already arrived, add it to the jobs in list.
|
||||
// Make sure it also hasn't already been completed, or isn't an in and out job.
|
||||
//This prevents the duplicate counting.
|
||||
load[itemDate].allJobsIn.push(item);
|
||||
if (AddJobForSchedulingCalc) {
|
||||
load[itemDate].jobsIn.push(item);
|
||||
load[itemDate].hoursIn =
|
||||
(load[itemDate].hoursIn || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
}
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
jobsIn: [item],
|
||||
allJobsIn: [item],
|
||||
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
|
||||
jobsOut: [],
|
||||
hoursIn:
|
||||
allJobsOut: [],
|
||||
allHoursIn:
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
hoursIn: AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
let problemJobs = [];
|
||||
compJobs.forEach((item) => {
|
||||
if (!(item.actual_completion || item.scheduled_completion))
|
||||
console.log("JOB HAS NO COMPLETION DATE.", item);
|
||||
console.warn("JOB HAS NO COMPLETION DATE.", item);
|
||||
|
||||
const inProdJobs = prodJobs.find((p) => p.id === item.id);
|
||||
const inArrJobs = arrJobs.find((p) => p.id === item.id);
|
||||
|
||||
if (!(inProdJobs || inArrJobs)) {
|
||||
//Job isn't found in production or coming in.
|
||||
//is it going today or scheduled to go today?
|
||||
if (
|
||||
moment(item.actual_completion || item.scheduled_completion).isSame(
|
||||
moment(),
|
||||
"day"
|
||||
)
|
||||
) {
|
||||
console.log("Job is going today anyways, ignore it.", item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
moment(item.actual_completion || item.scheduled_completion).isBefore(
|
||||
moment(),
|
||||
"day"
|
||||
)
|
||||
) {
|
||||
console.log("Job should have already gone. Ignoring it.", item);
|
||||
return;
|
||||
}
|
||||
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job is scheduled for completion, but it is not marked in production nor is it an arriving job in this period. Check the scheduled in and completion dates",
|
||||
});
|
||||
return;
|
||||
}
|
||||
const AddJobForSchedulingCalc = inProdJobs || inArrJobs;
|
||||
|
||||
const itemDate = moment(
|
||||
item.actual_completion || item.scheduled_completion
|
||||
).format("yyyy-MM-DD");
|
||||
//Skip it, it's already completed.
|
||||
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursOut =
|
||||
(load[itemDate].hoursOut || 0) +
|
||||
load[itemDate].allHoursOut =
|
||||
(load[itemDate].allHoursOut || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].jobsOut.push(item);
|
||||
//Add only the jobs that are still in production to get rid of.
|
||||
//If it's not in production, we'd subtract unnecessarily.
|
||||
load[itemDate].allJobsOut.push(item);
|
||||
|
||||
if (AddJobForSchedulingCalc) {
|
||||
load[itemDate].jobsOut.push(item);
|
||||
load[itemDate].hoursOut =
|
||||
(load[itemDate].hoursOut || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
}
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
jobsOut: [item],
|
||||
hoursOut:
|
||||
allJobsOut: [item],
|
||||
jobsOut: AddJobForSchedulingCalc ? [item] : [], //Same as above.
|
||||
hoursOut: AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
allHoursOut:
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
};
|
||||
@@ -137,7 +206,8 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
});
|
||||
|
||||
//Propagate the expected load to each day.
|
||||
const range = Math.round(moment.duration(end.diff(today)).asDays());
|
||||
|
||||
const range = Math.round(moment.duration(end.diff(today)).asDays()) + 1;
|
||||
for (var day = 0; day < range; day++) {
|
||||
const current = moment(today).add(day, "days").format("yyyy-MM-DD");
|
||||
const prev = moment(today)
|
||||
@@ -146,6 +216,7 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
if (!!!load[current]) {
|
||||
load[current] = {};
|
||||
}
|
||||
|
||||
if (day === 0) {
|
||||
//Starting on day 1. The load is current.
|
||||
load[current].expectedLoad = CalculateLoad(
|
||||
@@ -154,6 +225,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 +240,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) -
|
||||
|
||||
@@ -49,7 +49,8 @@
|
||||
"blocked": "Blocked",
|
||||
"cancelledappointment": "Canceled appointment for: ",
|
||||
"completingjobs": "Completing Jobs",
|
||||
"dataconsistency": "{{ro_number}} has a data consistency issue. It has been excluded for scheduling purposes. CODE: {{code}}.",
|
||||
"dataconsistency": "{{ro_number}} has a data consistency issue. It may have been excluded for scheduling purposes. CODE: {{code}}.",
|
||||
"expectedjobs": "Expected Jobs in Production: ",
|
||||
"expectedprodhrs": "Expected Production Hours:",
|
||||
"history": "History",
|
||||
"inproduction": "Jobs In Production",
|
||||
@@ -60,6 +61,7 @@
|
||||
"priorappointments": "Previous Appointments",
|
||||
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
|
||||
"scheduledfor": "Scheduled appointment for: ",
|
||||
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.",
|
||||
"smartscheduling": "Smart Scheduling",
|
||||
"suggesteddates": "Suggested Dates"
|
||||
},
|
||||
@@ -2424,6 +2426,9 @@
|
||||
"export_payables": "Export Log - Payables",
|
||||
"export_payments": "Export Log - Payments",
|
||||
"export_receivables": "Export Log - Receivables",
|
||||
"gsr_by_atp": "",
|
||||
"gsr_by_ats": "Gross Sales by ATS",
|
||||
"gsr_by_category": "Gross Sales by Category",
|
||||
"gsr_by_csr": "Gross Sales by CSR",
|
||||
"gsr_by_delivery_date": "Gross Sales by Delivery Date",
|
||||
"gsr_by_estimator": "Gross Sales by Estimator",
|
||||
@@ -2454,12 +2459,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",
|
||||
@@ -2478,6 +2486,8 @@
|
||||
"production_by_technician": "Production by Technician",
|
||||
"production_by_technician_one": "Production filtered by Technician",
|
||||
"psr_by_make": "Percent of Sales by Vehicle Make",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
||||
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
|
||||
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
|
||||
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
|
||||
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
||||
@@ -2489,6 +2499,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",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"cancelledappointment": "Cita cancelada para:",
|
||||
"completingjobs": "",
|
||||
"dataconsistency": "",
|
||||
"expectedjobs": "",
|
||||
"expectedprodhrs": "",
|
||||
"history": "",
|
||||
"inproduction": "",
|
||||
@@ -60,6 +61,7 @@
|
||||
"priorappointments": "Nombramientos previos",
|
||||
"reminder": "",
|
||||
"scheduledfor": "Cita programada para:",
|
||||
"severalerrorsfound": "",
|
||||
"smartscheduling": "",
|
||||
"suggesteddates": ""
|
||||
},
|
||||
@@ -2424,6 +2426,9 @@
|
||||
"export_payables": "",
|
||||
"export_payments": "",
|
||||
"export_receivables": "",
|
||||
"gsr_by_atp": "",
|
||||
"gsr_by_ats": "",
|
||||
"gsr_by_category": "",
|
||||
"gsr_by_csr": "",
|
||||
"gsr_by_delivery_date": "",
|
||||
"gsr_by_estimator": "",
|
||||
@@ -2454,12 +2459,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": "",
|
||||
@@ -2478,6 +2486,8 @@
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"psr_by_make": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
||||
"purchases_by_cost_center_detail": "",
|
||||
"purchases_by_cost_center_summary": "",
|
||||
"purchases_by_date_range_detail": "",
|
||||
@@ -2489,6 +2499,7 @@
|
||||
"returns_grouped_by_vendor_detailed": "",
|
||||
"returns_grouped_by_vendor_summary": "",
|
||||
"schedule": "",
|
||||
"scheduled_parts_list": "",
|
||||
"scoreboard_detail": "",
|
||||
"scoreboard_summary": "",
|
||||
"supplement_ratio_ins_co": "",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"cancelledappointment": "Rendez-vous annulé pour:",
|
||||
"completingjobs": "",
|
||||
"dataconsistency": "",
|
||||
"expectedjobs": "",
|
||||
"expectedprodhrs": "",
|
||||
"history": "",
|
||||
"inproduction": "",
|
||||
@@ -60,6 +61,7 @@
|
||||
"priorappointments": "Rendez-vous précédents",
|
||||
"reminder": "",
|
||||
"scheduledfor": "Rendez-vous prévu pour:",
|
||||
"severalerrorsfound": "",
|
||||
"smartscheduling": "",
|
||||
"suggesteddates": ""
|
||||
},
|
||||
@@ -2424,6 +2426,9 @@
|
||||
"export_payables": "",
|
||||
"export_payments": "",
|
||||
"export_receivables": "",
|
||||
"gsr_by_atp": "",
|
||||
"gsr_by_ats": "",
|
||||
"gsr_by_category": "",
|
||||
"gsr_by_csr": "",
|
||||
"gsr_by_delivery_date": "",
|
||||
"gsr_by_estimator": "",
|
||||
@@ -2454,12 +2459,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": "",
|
||||
@@ -2478,6 +2486,8 @@
|
||||
"production_by_technician": "",
|
||||
"production_by_technician_one": "",
|
||||
"psr_by_make": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
||||
"purchases_by_cost_center_detail": "",
|
||||
"purchases_by_cost_center_summary": "",
|
||||
"purchases_by_date_range_detail": "",
|
||||
@@ -2489,6 +2499,7 @@
|
||||
"returns_grouped_by_vendor_detailed": "",
|
||||
"returns_grouped_by_vendor_summary": "",
|
||||
"schedule": "",
|
||||
"scheduled_parts_list": "",
|
||||
"scoreboard_detail": "",
|
||||
"scoreboard_summary": "",
|
||||
"supplement_ratio_ins_co": "",
|
||||
|
||||
@@ -1343,6 +1343,32 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
gsr_by_category: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_category"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_category"),
|
||||
key: "gsr_by_category",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
gsr_by_ats: {
|
||||
title: i18n.t("reportcenter.templates.gsr_by_ats"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.gsr_by_ats"),
|
||||
key: "gsr_by_ats",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced"),
|
||||
},
|
||||
group: "sales",
|
||||
},
|
||||
gsr_labor_only: {
|
||||
title: i18n.t("reportcenter.templates.gsr_labor_only"),
|
||||
description: "",
|
||||
@@ -1421,6 +1447,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 +1773,74 @@ 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",
|
||||
},
|
||||
purchase_return_ratio_grouped_by_vendor_detail: {
|
||||
title: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"),
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.purchase_return_ratio_grouped_by_vendor_detail"
|
||||
),
|
||||
key: "purchase_return_ratio_grouped_by_vendor_detail",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.bills"),
|
||||
field: i18n.t("bills.fields.date"),
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
purchase_return_ratio_grouped_by_vendor_summary: {
|
||||
title: i18n.t("reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"),
|
||||
subject: i18n.t(
|
||||
"reportcenter.templates.purchase_return_ratio_grouped_by_vendor_summary"
|
||||
),
|
||||
key: "purchase_return_ratio_grouped_by_vendor_summary",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.bills"),
|
||||
field: i18n.t("bills.fields.date"),
|
||||
},
|
||||
group: "purchases",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "courtesycarcontract"
|
||||
|
||||
10
hasura/migrations/1674581461316_run_sql_migration/down.sql
Normal file
10
hasura/migrations/1674581461316_run_sql_migration/down.sql
Normal 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;
|
||||
8
hasura/migrations/1674581461316_run_sql_migration/up.sql
Normal file
8
hasura/migrations/1674581461316_run_sql_migration/up.sql
Normal 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;
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."exportlog_billid";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "exportlog_billid" on
|
||||
"public"."exportlog" using btree ("billid");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."exportlog_jobid";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "exportlog_jobid" on
|
||||
"public"."exportlog" using btree ("jobid");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."exportlog_payments";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "exportlog_payments" on
|
||||
"public"."exportlog" using btree ("paymentid");
|
||||
@@ -895,6 +895,12 @@ const CreateCosts = (job) => {
|
||||
GlassLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAG] || 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(),
|
||||
|
||||
@@ -23,6 +23,7 @@ let transporter = nodemailer.createTransport({
|
||||
});
|
||||
|
||||
exports.sendServerEmail = async function ({ subject, text }) {
|
||||
if (process.env.NODE_ENV === undefined) return;
|
||||
try {
|
||||
transporter.sendMail(
|
||||
{
|
||||
|
||||
@@ -536,7 +536,16 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
|
||||
arrJobs: jobs(where: {scheduled_in: {_gte: $now}, suspended: {_eq: false}}) {
|
||||
id
|
||||
scheduled_in
|
||||
actual_in
|
||||
scheduled_completion
|
||||
ro_number
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
alt_transport
|
||||
actual_completion
|
||||
inproduction
|
||||
status
|
||||
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||
aggregate {
|
||||
sum {
|
||||
@@ -554,9 +563,15 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
|
||||
}
|
||||
compJobs: jobs(where: {_and: [{suspended: {_eq: false}}, {_or: [{scheduled_completion: {_gte: $now}}, {actual_completion: {_gte: $now}}]}]}) {
|
||||
id
|
||||
status
|
||||
ro_number
|
||||
scheduled_completion
|
||||
actual_completion
|
||||
scheduled_in
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
inproduction
|
||||
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||
aggregate {
|
||||
sum {
|
||||
@@ -574,15 +589,20 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
|
||||
}
|
||||
prodJobs: jobs(where: {inproduction: {_eq: true}, suspended: {_eq: false}}) {
|
||||
id
|
||||
actual_in
|
||||
scheduled_in
|
||||
actual_completion
|
||||
scheduled_completion
|
||||
labhrs: joblines_aggregate(where: {_and: [{mod_lbr_ty: {_neq: "LAR"}}, {removed: {_eq: false}}]}) {
|
||||
inproduction
|
||||
ro_number
|
||||
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
}
|
||||
}
|
||||
}
|
||||
larhrs: joblines_aggregate(where: {_and: [{mod_lbr_ty: {_eq: "LAR"}}, {removed: {_eq: false}}]}) {
|
||||
larhrs: joblines_aggregate(where: {mod_lbr_ty: {_eq: "LAR"}, removed: {_eq: false}}) {
|
||||
aggregate {
|
||||
sum {
|
||||
mod_lb_hrs
|
||||
@@ -590,7 +610,8 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
|
||||
}
|
||||
}
|
||||
}
|
||||
}`;
|
||||
}
|
||||
`;
|
||||
|
||||
exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
|
||||
employees(where: {_and: {shopid: {_eq: $shopId}, employee_number: {_eq: $employeeId}}}) {
|
||||
|
||||
@@ -41,9 +41,9 @@ exports.job = async (req, res) => {
|
||||
(bucket) =>
|
||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
||||
)[0];
|
||||
|
||||
const load = {
|
||||
productionTotal: {},
|
||||
productionHours: 0,
|
||||
};
|
||||
//Set the current load.
|
||||
ssbuckets.forEach((bucket) => {
|
||||
@@ -70,27 +70,6 @@ exports.job = async (req, res) => {
|
||||
// );
|
||||
const filteredArrJobs = [];
|
||||
|
||||
// filteredArrJobs.forEach((item) => {
|
||||
// const itemDate = moment(item.scheduled_in)
|
||||
// .tz(timezone)
|
||||
// .format("yyyy-MM-DD");
|
||||
// if (!!load[itemDate]) {
|
||||
// load[itemDate].hoursIn =
|
||||
// (load[itemDate].hoursIn || 0) +
|
||||
// item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
// item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
// load[itemDate].jobsIn.push(item);
|
||||
// } else {
|
||||
// load[itemDate] = {
|
||||
// jobsIn: [item],
|
||||
// jobsOut: [],
|
||||
// hoursIn:
|
||||
// item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
// item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
// };
|
||||
// }
|
||||
// });
|
||||
|
||||
arrJobs.forEach((item) => {
|
||||
let isSameBucket = false;
|
||||
if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
|
||||
@@ -102,21 +81,27 @@ exports.job = async (req, res) => {
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
|
||||
const itemDate = moment(item.scheduled_in)
|
||||
const AddJobForSchedulingCalc = !item.inproduction;
|
||||
|
||||
const itemDate = moment(item.actual_in || item.scheduled_in)
|
||||
.tz(timezone)
|
||||
.format("yyyy-MM-DD");
|
||||
if (isSameBucket) {
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursIn = (load[itemDate].hoursIn || 0) + jobHours;
|
||||
load[itemDate].jobsIn.push(item);
|
||||
load[itemDate].hoursIn =
|
||||
(load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc
|
||||
? jobHours
|
||||
: 0;
|
||||
if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item);
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
jobsIn: [item],
|
||||
jobsIn: AddJobForSchedulingCalc ? [item] : [],
|
||||
jobsOut: [],
|
||||
hoursIn: jobHours,
|
||||
hoursIn: AddJobForSchedulingCalc ? jobHours : 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!load[itemDate]) {
|
||||
load[itemDate] = {
|
||||
jobsIn: [],
|
||||
@@ -139,52 +124,28 @@ exports.job = async (req, res) => {
|
||||
const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
|
||||
const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);
|
||||
|
||||
if (!(inProdJobs || inArrJobs)) {
|
||||
//Job isn't found in production or coming in.
|
||||
//is it going today or scheduled to go today?
|
||||
if (
|
||||
moment(item.actual_completion || item.scheduled_completion)
|
||||
.tz(timezone)
|
||||
.isSame(moment().tz(timezone), "day")
|
||||
) {
|
||||
console.log("Job is going today anyways, ignore it.", item);
|
||||
return;
|
||||
}
|
||||
const AddJobForSchedulingCalc = inProdJobs || inArrJobs;
|
||||
|
||||
if (
|
||||
moment(item.actual_completion || item.scheduled_completion)
|
||||
.tz(timezone)
|
||||
.isBefore(moment().tz(timezone), "day")
|
||||
) {
|
||||
console.log("Job should have already gone. Ignoring it.", item);
|
||||
return;
|
||||
}
|
||||
console.log("PROBLEM JOB", item);
|
||||
problemJobs.push({
|
||||
...item,
|
||||
code: "Job is scheduled for completion, but it is not marked in production nor is it an arriving job in this period. Check the scheduled in and completion dates",
|
||||
});
|
||||
return;
|
||||
const itemDate = moment(
|
||||
item.actual_completion || item.scheduled_completion
|
||||
)
|
||||
.tz(timezone)
|
||||
.format("yyyy-MM-DD");
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursOut =
|
||||
(load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0;
|
||||
if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item);
|
||||
} else {
|
||||
const itemDate = moment(
|
||||
item.actual_completion || item.scheduled_completion
|
||||
)
|
||||
.tz(timezone)
|
||||
.format("yyyy-MM-DD");
|
||||
if (!!load[itemDate]) {
|
||||
load[itemDate].hoursOut =
|
||||
(load[itemDate].hoursOut || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].jobsOut.push(item);
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
jobsOut: [item],
|
||||
hoursOut:
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
};
|
||||
}
|
||||
load[itemDate] = {
|
||||
jobsOut: AddJobForSchedulingCalc ? [item] : [],
|
||||
hoursOut: AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user