308 lines
9.8 KiB
JavaScript
308 lines
9.8 KiB
JavaScript
import moment from "moment";
|
|
import { all, call, put, select, takeLatest } from "redux-saga/effects";
|
|
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
|
|
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
|
|
import client from "../../utils/GraphQLClient";
|
|
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
|
|
import {
|
|
scheduleLoadFailure,
|
|
scheduleLoadSuccess,
|
|
setProblemJobs,
|
|
} from "./application.actions";
|
|
import ApplicationActionTypes from "./application.types";
|
|
|
|
export function* onCalculateScheduleLoad() {
|
|
yield takeLatest(
|
|
ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD,
|
|
calculateScheduleLoad
|
|
);
|
|
}
|
|
export function* calculateScheduleLoad({ payload: end }) {
|
|
//REMINDER: Moment.js is not immutable. Today WILL change when adjusted.
|
|
const today = moment().startOf("day");
|
|
const state = yield select();
|
|
const buckets = state.user.bodyshop.ssbuckets;
|
|
|
|
try {
|
|
const result = yield client.query({
|
|
query: QUERY_SCHEDULE_LOAD_DATA,
|
|
variables: {
|
|
start: today,
|
|
end: end,
|
|
},
|
|
});
|
|
const { prodJobs, arrJobs, compJobs } = result.data;
|
|
|
|
const load = {
|
|
productionTotal: {},
|
|
productionHours: 0,
|
|
};
|
|
let problemJobs = [];
|
|
//Set the current load.
|
|
buckets.forEach((bucket) => {
|
|
load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
|
|
});
|
|
|
|
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 +
|
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
|
if (bucketId) {
|
|
load.productionTotal[bucketId].count =
|
|
load.productionTotal[bucketId].count + 1;
|
|
} else {
|
|
console.log("Uh oh, this job doesn't fit in a bucket!", item);
|
|
}
|
|
});
|
|
|
|
arrJobs.forEach((item) => {
|
|
if (!item.scheduled_in) {
|
|
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
|
|
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].allHoursIn =
|
|
(load[itemDate].allHoursIn || 0) +
|
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
|
|
|
//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] = {
|
|
allJobsIn: [item],
|
|
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
|
|
jobsOut: [],
|
|
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,
|
|
};
|
|
}
|
|
});
|
|
|
|
compJobs.forEach((item) => {
|
|
if (!(item.actual_completion || item.scheduled_completion))
|
|
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);
|
|
|
|
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].allHoursOut =
|
|
(load[itemDate].allHoursOut || 0) +
|
|
item.labhrs.aggregate.sum.mod_lb_hrs +
|
|
item.larhrs.aggregate.sum.mod_lb_hrs;
|
|
//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] = {
|
|
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,
|
|
};
|
|
}
|
|
});
|
|
|
|
//Propagate the expected load to each day.
|
|
|
|
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)
|
|
.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,
|
|
buckets,
|
|
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) -
|
|
(load[current].hoursOut || 0);
|
|
} else {
|
|
load[current].expectedLoad = CalculateLoad(
|
|
load[prev].expectedLoad,
|
|
buckets,
|
|
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) -
|
|
(load[current].hoursOut || 0);
|
|
}
|
|
}
|
|
|
|
yield put(setProblemJobs(problemJobs));
|
|
yield put(scheduleLoadSuccess(load));
|
|
} catch (error) {
|
|
yield put(scheduleLoadFailure(error));
|
|
}
|
|
}
|
|
|
|
export function* onInsertAuditTrail() {
|
|
yield takeLatest(
|
|
ApplicationActionTypes.INSERT_AUDIT_TRAIL,
|
|
insertAuditTrailSaga
|
|
);
|
|
}
|
|
|
|
export function* insertAuditTrailSaga({
|
|
payload: { jobid, billid, operation, type },
|
|
}) {
|
|
const state = yield select();
|
|
const bodyshop = state.user.bodyshop;
|
|
const currentUser = state.user.currentUser;
|
|
|
|
const variables = {
|
|
auditObj: {
|
|
bodyshopid: bodyshop.id,
|
|
jobid,
|
|
billid,
|
|
operation,
|
|
type,
|
|
useremail: currentUser.email,
|
|
},
|
|
};
|
|
yield client.mutate({
|
|
mutation: INSERT_AUDIT_TRAIL,
|
|
variables,
|
|
update(cache, { data }) {
|
|
cache.modify({
|
|
fields: {
|
|
audit_trail(existingAuditTrail, { readField }) {
|
|
const newAuditTrail = cache.writeQuery({
|
|
data: data,
|
|
query: INSERT_AUDIT_TRAIL,
|
|
variables,
|
|
});
|
|
return [...existingAuditTrail, newAuditTrail];
|
|
},
|
|
},
|
|
});
|
|
},
|
|
});
|
|
}
|
|
|
|
export function* applicationSagas() {
|
|
yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]);
|
|
}
|