Merged in release/2022-01-07 (pull request #333)
release/2022-01-07 Approved-by: Patrick Fic
This commit is contained in:
@@ -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
|
BabelEdit project file
|
||||||
@@ -21431,6 +21431,27 @@
|
|||||||
</concept_node>
|
</concept_node>
|
||||||
</children>
|
</children>
|
||||||
</folder_node>
|
</folder_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>invoice_final_note</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>kmin</name>
|
<name>kmin</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -33986,6 +34007,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>folder_label_multiple</name>
|
||||||
|
<definition_loaded>false</definition_loaded>
|
||||||
|
<description></description>
|
||||||
|
<comment></comment>
|
||||||
|
<default_text></default_text>
|
||||||
|
<translations>
|
||||||
|
<translation>
|
||||||
|
<language>en-US</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>es-MX</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
<translation>
|
||||||
|
<language>fr-CA</language>
|
||||||
|
<approved>false</approved>
|
||||||
|
</translation>
|
||||||
|
</translations>
|
||||||
|
</concept_node>
|
||||||
<concept_node>
|
<concept_node>
|
||||||
<name>glass_express_checklist</name>
|
<name>glass_express_checklist</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -117,9 +117,9 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/webpack-plugin": "^1.18.3",
|
"@sentry/webpack-plugin": "^1.18.3",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"react-error-overlay": "6.0.9",
|
|
||||||
"cypress": "^9.1.1",
|
"cypress": "^9.1.1",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
|
"react-error-overlay": "6.0.9",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.2"
|
"source-map-explorer": "^2.5.2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
|
|||||||
context: restVals,
|
context: restVals,
|
||||||
},
|
},
|
||||||
{},
|
{},
|
||||||
"p"
|
"p",
|
||||||
|
jobId
|
||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIsModalVisible(false);
|
setIsModalVisible(false);
|
||||||
@@ -61,10 +62,10 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
|
|||||||
label={t("printcenter.jobs.labels.position")}
|
label={t("printcenter.jobs.labels.position")}
|
||||||
name="position"
|
name="position"
|
||||||
>
|
>
|
||||||
<InputNumber min={0} precision={0} />
|
<InputNumber min={1} precision={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("printcenter.jobs.labels.count")} name="count">
|
<Form.Item label={t("printcenter.jobs.labels.count")} name="count">
|
||||||
<InputNumber min={0} precision={0} />
|
<InputNumber min={1} precision={0} max={99} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" loading={loading} onClick={handleOk}>
|
<Button type="primary" loading={loading} onClick={handleOk}>
|
||||||
{t("general.actions.print")}
|
{t("general.actions.print")}
|
||||||
|
|||||||
@@ -10,13 +10,17 @@ import Event from "../job-at-change/schedule-event.container";
|
|||||||
import HeaderComponent from "./schedule-calendar-header.component";
|
import HeaderComponent from "./schedule-calendar-header.component";
|
||||||
import "./schedule-calendar.styles.scss";
|
import "./schedule-calendar.styles.scss";
|
||||||
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
|
||||||
|
import { selectProblemJobs } from "../../redux/application/application.selectors";
|
||||||
|
import { Alert } from "antd";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
problemJobs: selectProblemJobs,
|
||||||
});
|
});
|
||||||
const localizer = momentLocalizer(moment);
|
const localizer = momentLocalizer(moment);
|
||||||
export function ScheduleCalendarWrapperComponent({
|
export function ScheduleCalendarWrapperComponent({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
|
problemJobs,
|
||||||
data,
|
data,
|
||||||
refetch,
|
refetch,
|
||||||
defaultView,
|
defaultView,
|
||||||
@@ -48,6 +52,15 @@ export function ScheduleCalendarWrapperComponent({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<JobDetailCards />
|
<JobDetailCards />
|
||||||
|
{problemJobs &&
|
||||||
|
problemJobs.map((problem) => (
|
||||||
|
<Alert
|
||||||
|
key={problem.id}
|
||||||
|
type="error"
|
||||||
|
message={`${problem.ro_number} has a data consistency issue. It has been exlcuded for scheduling purposes. CODE: ${problem.code}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
<Calendar
|
<Calendar
|
||||||
events={data}
|
events={data}
|
||||||
defaultView={search.view || defaultView || "week"}
|
defaultView={search.view || defaultView || "week"}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/sched
|
|||||||
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
||||||
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||||
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
||||||
|
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
|
||||||
|
|
||||||
export default function ScheduleCalendarComponent({ data, refetch }) {
|
export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||||
return (
|
return (
|
||||||
@@ -15,6 +16,7 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
<ScheduleVerifyIntegrity />
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
refetch();
|
refetch();
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Row,
|
Row,
|
||||||
Select,
|
Select,
|
||||||
Space, Switch,
|
Space,
|
||||||
Typography
|
Switch,
|
||||||
|
Typography,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
@@ -75,10 +76,14 @@ export function ScheduleJobModalComponent({
|
|||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Space>
|
<Space>
|
||||||
<Typography.Title level={3}>{lbrHrsData?.jobs_by_pk?.ro_number}</Typography.Title>
|
<Typography.Title level={3}>
|
||||||
<Typography.Title
|
{lbrHrsData?.jobs_by_pk?.ro_number}
|
||||||
level={4}
|
</Typography.Title>
|
||||||
>{`B/R Hrs:${lbrHrsData?.jobs_by_pk.labhrs?.aggregate.sum.mod_lb_hrs}/${lbrHrsData?.jobs_by_pk.larhrs?.aggregate.sum.mod_lb_hrs}`}</Typography.Title>
|
<Typography.Title level={4}>{`B/R Hrs:${
|
||||||
|
lbrHrsData?.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0
|
||||||
|
}/${
|
||||||
|
lbrHrsData?.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0
|
||||||
|
}`}</Typography.Title>
|
||||||
</Space>
|
</Space>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useApolloClient } from "@apollo/client";
|
||||||
|
import { Button } from "antd";
|
||||||
|
import moment from "moment";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
|
||||||
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
currentUser: selectCurrentUser,
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(ScheduleVerifyIntegrity);
|
||||||
|
|
||||||
|
export function ScheduleVerifyIntegrity({ currentUser }) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const client = useApolloClient();
|
||||||
|
const handleVerify = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
const {
|
||||||
|
data: { arrJobs, compJobs, prodJobs },
|
||||||
|
} = await client.query({
|
||||||
|
query: QUERY_SCHEDULE_LOAD_DATA,
|
||||||
|
variables: { start: moment(), end: moment().add(180, "days") },
|
||||||
|
});
|
||||||
|
|
||||||
|
//check that the leaving jobs are either in the arriving list, or in production.
|
||||||
|
const issues = [];
|
||||||
|
|
||||||
|
compJobs.forEach((j) => {
|
||||||
|
const inProdJobs = prodJobs.find((p) => p.id === j.id);
|
||||||
|
const inArrJobs = arrJobs.find((p) => p.id === j.id);
|
||||||
|
|
||||||
|
if (!(inProdJobs || inArrJobs)) {
|
||||||
|
// NOT FOUND!
|
||||||
|
issues.push(j);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
"The following completing jobs are not in production, or are arriving within the next 180 days. ",
|
||||||
|
issues
|
||||||
|
);
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (currentUser.email === "patrick@imex.prod")
|
||||||
|
return (
|
||||||
|
<Button loading={loading} onClick={handleVerify}>
|
||||||
|
Developer Use Only - Verify Schedule Integrity
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
@@ -234,11 +234,17 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
compJobs: jobs(
|
compJobs: jobs(
|
||||||
where: { scheduled_completion: { _gte: $start, _lte: $end } }
|
where: {
|
||||||
|
_or: [
|
||||||
|
{ scheduled_completion: { _gte: $start, _lte: $end } }
|
||||||
|
{ actual_completion: { _gte: $start, _lte: $end } }
|
||||||
|
]
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
id
|
id
|
||||||
ro_number
|
ro_number
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
|
actual_completion
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(
|
||||||
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1816,6 +1816,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
jobs_by_pk(id: $id) {
|
jobs_by_pk(id: $id) {
|
||||||
ro_number
|
ro_number
|
||||||
invoice_allocation
|
invoice_allocation
|
||||||
|
invoice_final_note
|
||||||
ins_co_id
|
ins_co_id
|
||||||
dms_allocation
|
dms_allocation
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
kmin: values.kmin,
|
kmin: values.kmin,
|
||||||
kmout: values.kmout,
|
kmout: values.kmout,
|
||||||
dms_allocation: values.dms_allocation,
|
dms_allocation: values.dms_allocation,
|
||||||
|
invoice_final_note: values.invoice_final_note,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
|
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
|
||||||
@@ -122,6 +123,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
kmin: job.kmin,
|
kmin: job.kmin,
|
||||||
kmout: job.kmout,
|
kmout: job.kmout,
|
||||||
dms_allocation: job.dms_allocation,
|
dms_allocation: job.dms_allocation,
|
||||||
|
invoice_final_note: job.invoice_final_note,
|
||||||
}}
|
}}
|
||||||
scrollToFirstError
|
scrollToFirstError
|
||||||
>
|
>
|
||||||
@@ -271,6 +273,12 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
|||||||
<Input disabled />
|
<Input disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
<Form.Item
|
||||||
|
label={t("jobs.fields.invoice_final_note")}
|
||||||
|
name="invoice_final_note"
|
||||||
|
>
|
||||||
|
<Input.TextArea disabled={jobRO} />
|
||||||
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider />
|
<Divider />
|
||||||
<JobsCloseLines job={job} />
|
<JobsCloseLines job={job} />
|
||||||
|
|||||||
@@ -58,3 +58,7 @@ export const insertAuditTrail = ({ jobid, billid, operation }) => ({
|
|||||||
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
||||||
payload: { jobid, billid, operation },
|
payload: { jobid, billid, operation },
|
||||||
});
|
});
|
||||||
|
export const setProblemJobs = (problemJobs) => ({
|
||||||
|
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
|
||||||
|
payload: problemJobs,
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const INITIAL_STATE = {
|
|||||||
breadcrumbs: [],
|
breadcrumbs: [],
|
||||||
recentItems: [],
|
recentItems: [],
|
||||||
selectedHeader: "home",
|
selectedHeader: "home",
|
||||||
|
problemJobs: [],
|
||||||
scheduleLoad: {
|
scheduleLoad: {
|
||||||
load: {},
|
load: {},
|
||||||
calculating: false,
|
calculating: false,
|
||||||
@@ -40,6 +41,7 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
|||||||
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
|
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
problemJobs: [],
|
||||||
scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null },
|
scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null },
|
||||||
};
|
};
|
||||||
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
|
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
|
||||||
@@ -76,7 +78,9 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
|||||||
|
|
||||||
case ApplicationActionTypes.SET_PARTNER_VERSION:
|
case ApplicationActionTypes.SET_PARTNER_VERSION:
|
||||||
return { ...state, partnerVersion: action.payload };
|
return { ...state, partnerVersion: action.payload };
|
||||||
|
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
|
||||||
|
return { ...state, problemJobs: action.payload };
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
|
|||||||
import {
|
import {
|
||||||
scheduleLoadFailure,
|
scheduleLoadFailure,
|
||||||
scheduleLoadSuccess,
|
scheduleLoadSuccess,
|
||||||
|
setProblemJobs,
|
||||||
} from "./application.actions";
|
} from "./application.actions";
|
||||||
import ApplicationActionTypes from "./application.types";
|
import ApplicationActionTypes from "./application.types";
|
||||||
|
|
||||||
@@ -53,6 +54,8 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
arrJobs.forEach((item) => {
|
arrJobs.forEach((item) => {
|
||||||
|
if (!item.scheduled_in)
|
||||||
|
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
|
||||||
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
|
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
|
||||||
if (!!load[itemDate]) {
|
if (!!load[itemDate]) {
|
||||||
load[itemDate].hoursIn =
|
load[itemDate].hoursIn =
|
||||||
@@ -71,7 +74,26 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let problemJobs = [];
|
||||||
compJobs.forEach((item) => {
|
compJobs.forEach((item) => {
|
||||||
|
if (!item.scheduled_completion)
|
||||||
|
console.log("JOB HAS NO SCHEDULED COMPLETION DATE.", item);
|
||||||
|
|
||||||
|
const inProdJobs = prodJobs.find((p) => p.id === item.id);
|
||||||
|
const inArrJobs = arrJobs.find((p) => p.id === item.id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(inProdJobs || inArrJobs) &&
|
||||||
|
!moment(item.scheduled_completion).isSame(moment(), "day")
|
||||||
|
) {
|
||||||
|
// NOT FOUND!
|
||||||
|
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.scheduled_completion).format("yyyy-MM-DD");
|
const itemDate = moment(item.scheduled_completion).format("yyyy-MM-DD");
|
||||||
if (!!load[itemDate]) {
|
if (!!load[itemDate]) {
|
||||||
load[itemDate].hoursOut =
|
load[itemDate].hoursOut =
|
||||||
@@ -116,7 +138,7 @@ export function* calculateScheduleLoad({ payload: end }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
yield put(setProblemJobs(problemJobs));
|
||||||
yield put(scheduleLoadSuccess(load));
|
yield put(scheduleLoadSuccess(load));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
yield put(scheduleLoadFailure(error));
|
yield put(scheduleLoadFailure(error));
|
||||||
|
|||||||
@@ -44,3 +44,7 @@ export const selectOnline = createSelector(
|
|||||||
[selectApplication],
|
[selectApplication],
|
||||||
(application) => application.online
|
(application) => application.online
|
||||||
);
|
);
|
||||||
|
export const selectProblemJobs = createSelector(
|
||||||
|
[selectApplication],
|
||||||
|
(application) => application.problemJobs
|
||||||
|
);
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ const ApplicationActionTypes = {
|
|||||||
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
|
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
|
||||||
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
|
||||||
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
|
||||||
|
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
|
||||||
};
|
};
|
||||||
export default ApplicationActionTypes;
|
export default ApplicationActionTypes;
|
||||||
|
|||||||
@@ -1298,6 +1298,7 @@
|
|||||||
"required": "Required?",
|
"required": "Required?",
|
||||||
"type": "Type"
|
"type": "Type"
|
||||||
},
|
},
|
||||||
|
"invoice_final_note": "Note to Display on Final Invoice",
|
||||||
"kmin": "Mileage In",
|
"kmin": "Mileage In",
|
||||||
"kmout": "Mileage Out",
|
"kmout": "Mileage Out",
|
||||||
"la1": "LA1",
|
"la1": "LA1",
|
||||||
@@ -2028,6 +2029,7 @@
|
|||||||
"filing_coversheet_portrait": "Filing Coversheet (Portrait)",
|
"filing_coversheet_portrait": "Filing Coversheet (Portrait)",
|
||||||
"final_invoice": "Final Invoice",
|
"final_invoice": "Final Invoice",
|
||||||
"fippa_authorization": "FIPPA Authorization",
|
"fippa_authorization": "FIPPA Authorization",
|
||||||
|
"folder_label_multiple": "Folder Label Multiple",
|
||||||
"glass_express_checklist": "Glass Express Checklist",
|
"glass_express_checklist": "Glass Express Checklist",
|
||||||
"guarantee": "Repair Guarantee",
|
"guarantee": "Repair Guarantee",
|
||||||
"individual_job_note": "Job Note RO # {{ro_number}}",
|
"individual_job_note": "Job Note RO # {{ro_number}}",
|
||||||
@@ -2097,7 +2099,7 @@
|
|||||||
},
|
},
|
||||||
"subjects": {
|
"subjects": {
|
||||||
"jobs": {
|
"jobs": {
|
||||||
"parts_order": "Parts Order PO: {{ro_number}}"
|
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vendors": {
|
"vendors": {
|
||||||
|
|||||||
@@ -1298,6 +1298,7 @@
|
|||||||
"required": "",
|
"required": "",
|
||||||
"type": ""
|
"type": ""
|
||||||
},
|
},
|
||||||
|
"invoice_final_note": "",
|
||||||
"kmin": "Kilometraje en",
|
"kmin": "Kilometraje en",
|
||||||
"kmout": "Kilometraje",
|
"kmout": "Kilometraje",
|
||||||
"la1": "",
|
"la1": "",
|
||||||
@@ -2028,6 +2029,7 @@
|
|||||||
"filing_coversheet_portrait": "",
|
"filing_coversheet_portrait": "",
|
||||||
"final_invoice": "",
|
"final_invoice": "",
|
||||||
"fippa_authorization": "",
|
"fippa_authorization": "",
|
||||||
|
"folder_label_multiple": "",
|
||||||
"glass_express_checklist": "",
|
"glass_express_checklist": "",
|
||||||
"guarantee": "",
|
"guarantee": "",
|
||||||
"individual_job_note": "",
|
"individual_job_note": "",
|
||||||
|
|||||||
@@ -1298,6 +1298,7 @@
|
|||||||
"required": "",
|
"required": "",
|
||||||
"type": ""
|
"type": ""
|
||||||
},
|
},
|
||||||
|
"invoice_final_note": "",
|
||||||
"kmin": "Kilométrage en",
|
"kmin": "Kilométrage en",
|
||||||
"kmout": "Kilométrage hors",
|
"kmout": "Kilométrage hors",
|
||||||
"la1": "",
|
"la1": "",
|
||||||
@@ -2028,6 +2029,7 @@
|
|||||||
"filing_coversheet_portrait": "",
|
"filing_coversheet_portrait": "",
|
||||||
"final_invoice": "",
|
"final_invoice": "",
|
||||||
"fippa_authorization": "",
|
"fippa_authorization": "",
|
||||||
|
"folder_label_multiple": "",
|
||||||
"glass_express_checklist": "",
|
"glass_express_checklist": "",
|
||||||
"guarantee": "",
|
"guarantee": "",
|
||||||
"individual_job_note": "",
|
"individual_job_note": "",
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
|
|||||||
const bucketId = CheckJobBucket(buckets, job);
|
const bucketId = CheckJobBucket(buckets, job);
|
||||||
if (bucketId) {
|
if (bucketId) {
|
||||||
newLoad[bucketId].count = newLoad[bucketId].count - 1;
|
newLoad[bucketId].count = newLoad[bucketId].count - 1;
|
||||||
|
if (newLoad[bucketId].count < 0) {
|
||||||
|
console.log("***ERROR: NEGATIVE LOAD", bucketId, job);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
|
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
|
||||||
|
|||||||
@@ -509,6 +509,11 @@ export const TemplateList = (type, context) => {
|
|||||||
key: "parts_order",
|
key: "parts_order",
|
||||||
subject: i18n.t("printcenter.subjects.jobs.parts_order", {
|
subject: i18n.t("printcenter.subjects.jobs.parts_order", {
|
||||||
ro_number: context && context.job && context.job.ro_number,
|
ro_number: context && context.job && context.job.ro_number,
|
||||||
|
name: `${(context && context.job && context.job.ownr_fn) || ""} ${
|
||||||
|
(context && context.job && context.job.ownr_ln) || ""
|
||||||
|
} ${
|
||||||
|
(context && context.job && context.job.ownr_co_nm) || ""
|
||||||
|
}`.trim(),
|
||||||
}),
|
}),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2694,6 +2694,7 @@
|
|||||||
- intakechecklist
|
- intakechecklist
|
||||||
- invoice_allocation
|
- invoice_allocation
|
||||||
- invoice_date
|
- invoice_date
|
||||||
|
- invoice_final_note
|
||||||
- iouparent
|
- iouparent
|
||||||
- job_totals
|
- job_totals
|
||||||
- kanbanparent
|
- kanbanparent
|
||||||
@@ -2948,6 +2949,7 @@
|
|||||||
- intakechecklist
|
- intakechecklist
|
||||||
- invoice_allocation
|
- invoice_allocation
|
||||||
- invoice_date
|
- invoice_date
|
||||||
|
- invoice_final_note
|
||||||
- iouparent
|
- iouparent
|
||||||
- job_totals
|
- job_totals
|
||||||
- kanbanparent
|
- kanbanparent
|
||||||
@@ -3212,6 +3214,7 @@
|
|||||||
- intakechecklist
|
- intakechecklist
|
||||||
- invoice_allocation
|
- invoice_allocation
|
||||||
- invoice_date
|
- invoice_date
|
||||||
|
- invoice_final_note
|
||||||
- iouparent
|
- iouparent
|
||||||
- job_totals
|
- job_totals
|
||||||
- kanbanparent
|
- kanbanparent
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- alter table "public"."jobs" add column "invoice_final_note" Text
|
||||||
|
-- null;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."jobs" add column "invoice_final_note" Text
|
||||||
|
null;
|
||||||
@@ -454,15 +454,14 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports.QUERY_UPCOMING_APPOINTMENTS = `
|
exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
||||||
query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
|
||||||
jobs_by_pk(id: $jobId) {
|
jobs_by_pk(id: $jobId) {
|
||||||
bodyshop {
|
bodyshop {
|
||||||
ssbuckets
|
ssbuckets
|
||||||
target_touchtime
|
target_touchtime
|
||||||
workingdays
|
workingdays
|
||||||
}
|
}
|
||||||
jobhrs: joblines_aggregate(where: { removed: { _eq: false } }) {
|
jobhrs: joblines_aggregate(where: {removed: {_eq: false}}) {
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
@@ -470,40 +469,60 @@ query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
appointments(where: {_and: {canceled: {_eq: false}, start: {_gte: $now}}}) {
|
blockedDays: appointments(where: {_and: {canceled: {_eq: false}, block: {_eq: true}, start: {_gte: $now}}}) {
|
||||||
start
|
start
|
||||||
isintake
|
|
||||||
id
|
|
||||||
block
|
block
|
||||||
job {
|
}
|
||||||
joblines_aggregate(where: { removed: { _eq: false } }) {
|
arrJobs: jobs(where: {scheduled_in: {_gte: $now}}) {
|
||||||
aggregate {
|
id
|
||||||
sum {
|
scheduled_in
|
||||||
mod_lb_hrs
|
ro_number
|
||||||
}
|
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
larhrs: joblines_aggregate(where: {mod_lbr_ty: {_eq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
jobs(where: {inproduction: {_eq: true}}) {
|
compJobs: jobs(where: {_or: [{scheduled_completion: {_gte: $now}}, {actual_completion: {_gte: $now}}]}) {
|
||||||
|
id
|
||||||
|
ro_number
|
||||||
|
scheduled_completion
|
||||||
|
actual_completion
|
||||||
|
labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
larhrs: joblines_aggregate(where: {mod_lbr_ty: {_eq: "LAR"}, removed: {_eq: false}}) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prodJobs: jobs(where: {inproduction: {_eq: true}}) {
|
||||||
id
|
id
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
labhrs: joblines_aggregate(
|
labhrs: joblines_aggregate(where: {_and: [{mod_lbr_ty: {_neq: "LAR"}}, {removed: {_eq: false}}]}) {
|
||||||
where: {
|
|
||||||
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
larhrs: joblines_aggregate(
|
larhrs: joblines_aggregate(where: {_and: [{mod_lbr_ty: {_eq: "LAR"}}, {removed: {_eq: false}}]}) {
|
||||||
where: {
|
|
||||||
_and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }]
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
aggregate {
|
aggregate {
|
||||||
sum {
|
sum {
|
||||||
mod_lb_hrs
|
mod_lb_hrs
|
||||||
@@ -512,6 +531,9 @@ query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
|
exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const queries = require("../graphql-client/queries");
|
|||||||
const Dinero = require("dinero.js");
|
const Dinero = require("dinero.js");
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
|
const _ = require("lodash");
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
@@ -30,7 +31,7 @@ exports.job = async (req, res) => {
|
|||||||
jobId: jobId,
|
jobId: jobId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { appointments, jobs } = result;
|
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
|
||||||
const { ssbuckets, workingdays } = result.jobs_by_pk.bodyshop;
|
const { ssbuckets, workingdays } = result.jobs_by_pk.bodyshop;
|
||||||
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
|
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
|
||||||
@@ -39,105 +40,164 @@ exports.job = async (req, res) => {
|
|||||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
||||||
)[0];
|
)[0];
|
||||||
|
|
||||||
const bucketMatrix = {};
|
const load = {
|
||||||
const yesterday = moment().subtract(1, "day");
|
productionTotal: {},
|
||||||
//Get latest date + add 5 days to allow for back end adding..
|
};
|
||||||
|
//Set the current load.
|
||||||
|
ssbuckets.forEach((bucket) => {
|
||||||
|
load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
|
||||||
|
});
|
||||||
|
|
||||||
const totalMatrixDays = moment
|
const filteredProdJobsList = prodJobs.filter(
|
||||||
.max([
|
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||||
...appointments.map((a) => moment(a.start)),
|
);
|
||||||
...jobs
|
|
||||||
.map((p) => moment(p.scheduled_completion))
|
|
||||||
.filter((p) => p.isValid() && p.isAfter(yesterday)),
|
|
||||||
])
|
|
||||||
.add("5", "days")
|
|
||||||
.diff(moment(), "days");
|
|
||||||
|
|
||||||
//Initialize the bucket matrix
|
filteredProdJobsList.forEach((item) => {
|
||||||
for (var i = 0; i < totalMatrixDays; i++) {
|
//Add all of the jobs currently in production to the buckets so that we have a starting point.
|
||||||
const theDate = moment().add(i, "days").format("yyyy-MM-DD");
|
const bucketId = CheckJobBucket(ssbuckets, item);
|
||||||
//Only need to create a matrix for jobs of the same bucket.
|
if (bucketId) {
|
||||||
bucketMatrix[theDate] = { in: 0, out: 0 };
|
load.productionTotal[bucketId].count =
|
||||||
|
load.productionTotal[bucketId].count + 1;
|
||||||
// ssbuckets.forEach((bucket) => {
|
|
||||||
// bucketMatrix[theDate] = {
|
|
||||||
// ...bucketMatrix[theDate],
|
|
||||||
// [bucket.id]: { in: 0, out: 0 },
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
//Populate the jobs scheduled to come in.
|
|
||||||
appointments.forEach((appointment) => {
|
|
||||||
if (!appointment.block) {
|
|
||||||
const jobHrs =
|
|
||||||
appointment.job.joblines_aggregate.aggregate.sum.mod_lb_hrs;
|
|
||||||
//Is the job in the same bucket?
|
|
||||||
const appointmentBucket = ssbuckets.filter(
|
|
||||||
(bucket) =>
|
|
||||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
|
||||||
)[0];
|
|
||||||
if (appointmentBucket.id === JobBucket.id) {
|
|
||||||
//Theyre the same classification. Add it to the matrix.
|
|
||||||
const appDate = moment(appointment.start).format("yyyy-MM-DD");
|
|
||||||
bucketMatrix[appDate] = {
|
|
||||||
...bucketMatrix[appDate],
|
|
||||||
in: bucketMatrix[appDate].in + 1,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//remove the date from the possible list.
|
console.log("Uh oh, this job doesn't fit in a bucket!", item);
|
||||||
const appDate = moment(appointment.start).format("yyyy-MM-DD");
|
}
|
||||||
bucketMatrix[appDate] = {
|
});
|
||||||
...bucketMatrix[appDate],
|
|
||||||
blocked: true,
|
const filteredArrJobs = arrJobs.filter(
|
||||||
|
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||||
|
);
|
||||||
|
|
||||||
|
filteredArrJobs.forEach((item) => {
|
||||||
|
const itemDate = moment(item.scheduled_in).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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//Populate the jobs that are leaving today.
|
//Get the completing jobs.
|
||||||
const todayIsoString = moment().format("yyyy-MM-DD");
|
let problemJobs = [];
|
||||||
jobs.forEach((pjob) => {
|
const filteredCompJobs = compJobs.filter(
|
||||||
const jobHrs =
|
(j) => JobBucket.id === CheckJobBucket(ssbuckets, j)
|
||||||
pjob.larhrs.aggregate.sum.mod_lb_hrs +
|
);
|
||||||
pjob.labhrs.aggregate.sum.mod_lb_hrs;
|
|
||||||
//Is the job in the same bucket?
|
|
||||||
const pjobBucket = ssbuckets.filter(
|
|
||||||
(bucket) =>
|
|
||||||
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
|
|
||||||
)[0];
|
|
||||||
if (pjobBucket.id === JobBucket.id) {
|
|
||||||
//Theyre the same classification. Add it to the matrix.
|
|
||||||
const compDate = moment(pjob.scheduled_completion);
|
|
||||||
//Is the schedule completion behind today? If so, use today as it.
|
|
||||||
let dateToUse;
|
|
||||||
dateToUse = compDate.isValid()
|
|
||||||
? moment().diff(compDate, "days") < 0
|
|
||||||
? compDate.format("yyyy-MM-DD")
|
|
||||||
: todayIsoString
|
|
||||||
: todayIsoString;
|
|
||||||
|
|
||||||
bucketMatrix[dateToUse] = {
|
filteredCompJobs.forEach((item) => {
|
||||||
...bucketMatrix[dateToUse],
|
const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
|
||||||
out: (bucketMatrix[dateToUse].out || 0) + 1,
|
const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//Propose the first 5 dates where we are below target.
|
|
||||||
|
|
||||||
const possibleDates = [];
|
|
||||||
const bucketMatrixKeys = Object.keys(bucketMatrix);
|
|
||||||
bucketMatrixKeys.forEach((bmkey) => {
|
|
||||||
const isShopOpen =
|
|
||||||
workingdays[dayOfWeekMapper(moment(bmkey).day())] &&
|
|
||||||
!bucketMatrix[bmkey].blocked;
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
JobBucket.target > bucketMatrix[bmkey].in - bucketMatrix[bmkey].out &&
|
!(inProdJobs || inArrJobs) &&
|
||||||
|
!moment(item.actual_completion || item.scheduled_completion).isSame(
|
||||||
|
moment(),
|
||||||
|
"day"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// NOT FOUND!
|
||||||
|
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;
|
||||||
|
} else {
|
||||||
|
const itemDate = moment(
|
||||||
|
item.actual_completion || item.scheduled_completion
|
||||||
|
).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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Propagate the expected load to each day.
|
||||||
|
const yesterday = moment().subtract(1, "day");
|
||||||
|
const today = moment().startOf("day");
|
||||||
|
|
||||||
|
const end = moment.max([
|
||||||
|
...filteredArrJobs.map((a) => moment(a.scheduled_in)),
|
||||||
|
...filteredCompJobs
|
||||||
|
.map((p) => moment(p.actual_completion || p.scheduled_completion))
|
||||||
|
.filter((p) => p.isValid() && p.isAfter(yesterday)),
|
||||||
|
]);
|
||||||
|
const range = Math.round(moment.duration(end.diff(today)).asDays());
|
||||||
|
for (var day = 0; day < range; day++) {
|
||||||
|
const current = moment(today).add(day, "days").format("yyyy-MM-DD");
|
||||||
|
const prev = moment(today)
|
||||||
|
.add(day - 1, "days")
|
||||||
|
.format("yyyy-MM-DD");
|
||||||
|
if (!!!load[current]) {
|
||||||
|
load[current] = {};
|
||||||
|
}
|
||||||
|
if (day === 0) {
|
||||||
|
//Starting on day 1. The load is current.
|
||||||
|
load[current].expectedLoad = CalculateLoad(
|
||||||
|
load.productionTotal,
|
||||||
|
ssbuckets,
|
||||||
|
load[current].jobsIn || [],
|
||||||
|
load[current].jobsOut || []
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
load[current].expectedLoad = CalculateLoad(
|
||||||
|
load[prev].expectedLoad,
|
||||||
|
ssbuckets,
|
||||||
|
load[current].jobsIn || [],
|
||||||
|
load[current].jobsOut || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add in all of the blocked days.
|
||||||
|
|
||||||
|
blockedDays.forEach((b) => {
|
||||||
|
//Find it in the load, set it as blocked.
|
||||||
|
const startIsoFormat = moment(b.start).format("YYYY-MM-DD");
|
||||||
|
if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
|
||||||
|
else {
|
||||||
|
load[startIsoFormat] = { blocked: true };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// //Propose the first 5 dates where we are below target.
|
||||||
|
|
||||||
|
const possibleDates = [];
|
||||||
|
delete load.productionTotal;
|
||||||
|
const loadKeys = Object.keys(load).sort((a, b) =>
|
||||||
|
moment(a).isAfter(moment(b)) ? 1 : -1
|
||||||
|
);
|
||||||
|
|
||||||
|
loadKeys.forEach((loadKey) => {
|
||||||
|
const isShopOpen =
|
||||||
|
(workingdays[dayOfWeekMapper(moment(loadKey).day())] || false) &&
|
||||||
|
!load[loadKey].blocked;
|
||||||
|
|
||||||
|
if (
|
||||||
|
load[loadKey].expectedLoad &&
|
||||||
|
load[loadKey].expectedLoad[JobBucket.id] &&
|
||||||
|
JobBucket.target > load[loadKey].expectedLoad[JobBucket.id].count &&
|
||||||
isShopOpen
|
isShopOpen
|
||||||
)
|
)
|
||||||
possibleDates.push(new Date(bmkey).toISOString().substr(0, 10));
|
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
|
||||||
});
|
});
|
||||||
|
|
||||||
if (possibleDates.length < 6) {
|
if (possibleDates.length < 6) {
|
||||||
@@ -147,7 +207,7 @@ exports.job = async (req, res) => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
|
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
|
||||||
error: JSON.stringify(error),
|
error,
|
||||||
});
|
});
|
||||||
res.status(400).send(error);
|
res.status(400).send(error);
|
||||||
}
|
}
|
||||||
@@ -171,3 +231,47 @@ const dayOfWeekMapper = (numberOfDay) => {
|
|||||||
return "saturday";
|
return "saturday";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CheckJobBucket = (buckets, job) => {
|
||||||
|
const jobHours =
|
||||||
|
job.labhrs.aggregate.sum.mod_lb_hrs + job.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
|
||||||
|
const matchingBucket = buckets.filter((b) =>
|
||||||
|
b.gte <= jobHours && b.lt ? b.lt > jobHours : true
|
||||||
|
);
|
||||||
|
|
||||||
|
return matchingBucket[0] && matchingBucket[0].id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
|
||||||
|
//Add the jobs coming
|
||||||
|
const newLoad = _.cloneDeep(currentLoad);
|
||||||
|
jobsIn.forEach((job) => {
|
||||||
|
const bucketId = CheckJobBucket(buckets, job);
|
||||||
|
if (bucketId) {
|
||||||
|
newLoad[bucketId].count = newLoad[bucketId].count + 1;
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"[Util Arr Job]Uh oh, this job doesn't fit in a bucket!",
|
||||||
|
job
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
jobsOut.forEach((job) => {
|
||||||
|
const bucketId = CheckJobBucket(buckets, job);
|
||||||
|
if (bucketId) {
|
||||||
|
newLoad[bucketId].count = newLoad[bucketId].count - 1;
|
||||||
|
if (newLoad[bucketId].count < 0) {
|
||||||
|
console.log("***ERROR: NEGATIVE LOAD Bucket =>", bucketId, job);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",
|
||||||
|
job
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return newLoad;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user