Merged in release/2023-02-03 (pull request #670)

release/2023-02-03

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2023-02-04 01:20:00 +00:00
15 changed files with 355 additions and 133 deletions

View File

@@ -967,6 +967,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>smartscheduling</name> <name>smartscheduling</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -40880,6 +40901,69 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>gsr_by_csr</name> <name>gsr_by_csr</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -22,10 +22,6 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) { export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
console.log(
"🚀 ~ file: schedule-calendar-header-graph.component.js:23 ~ ScheduleCalendarHeaderGraph ~ loadData",
loadData
);
const { ssbuckets } = bodyshop; const { ssbuckets } = bodyshop;
const { t } = useTranslation(); const { t } = useTranslation();
const data = useMemo(() => { const data = useMemo(() => {

View File

@@ -66,8 +66,8 @@ export function ScheduleCalendarHeaderComponent({
<div onClick={(e) => e.stopPropagation()}> <div onClick={(e) => e.stopPropagation()}>
<table> <table>
<tbody> <tbody>
{loadData && loadData.jobsOut ? ( {loadData && loadData.allJobsOut ? (
loadData.jobsOut.map((j) => ( loadData.allJobsOut.map((j) => (
<tr key={j.id}> <tr key={j.id}>
<td> <td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
@@ -102,11 +102,12 @@ export function ScheduleCalendarHeaderComponent({
<div onClick={(e) => e.stopPropagation()}> <div onClick={(e) => e.stopPropagation()}>
<table> <table>
<tbody> <tbody>
{loadData && loadData.jobsIn ? ( {loadData && loadData.allJobsIn ? (
loadData.jobsIn.map((j) => ( loadData.allJobsIn.map((j) => (
<tr key={j.id}> <tr key={j.id}>
<td> <td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
{j.status}
</td> </td>
<td> <td>
<OwnerNameDisplay ownerObject={j} /> <OwnerNameDisplay ownerObject={j} />
@@ -142,7 +143,7 @@ export function ScheduleCalendarHeaderComponent({
title={t("appointments.labels.arrivingjobs")} title={t("appointments.labels.arrivingjobs")}
> >
<Icon component={MdFileDownload} style={{ color: "green" }} /> <Icon component={MdFileDownload} style={{ color: "green" }} />
{(loadData.hoursIn || 0) && loadData.hoursIn.toFixed(2)} {(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
</Popover> </Popover>
<Popover <Popover
placement={"bottom"} placement={"bottom"}
@@ -151,7 +152,7 @@ export function ScheduleCalendarHeaderComponent({
title={t("appointments.labels.completingjobs")} title={t("appointments.labels.completingjobs")}
> >
<Icon component={MdFileUpload} style={{ color: "red" }} /> <Icon component={MdFileUpload} style={{ color: "red" }} />
{(loadData.hoursOut || 0) && loadData.hoursOut.toFixed(2)} {(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
</Popover> </Popover>
<ScheduleCalendarHeaderGraph loadData={loadData} /> <ScheduleCalendarHeaderGraph loadData={loadData} />
</div> </div>

View File

@@ -11,7 +11,7 @@ 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 { selectProblemJobs } from "../../redux/application/application.selectors";
import { Alert } from "antd"; import { Alert, Collapse } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -53,8 +53,16 @@ export function ScheduleCalendarWrapperComponent({
return ( return (
<> <>
<JobDetailCards /> <JobDetailCards />
{problemJobs && {problemJobs && problemJobs.length > 2 ? (
problemJobs.map((problem) => ( <Collapse>
<Collapse.Panel
header={
<span style={{ color: "tomato" }}>
{t("appointments.labels.severalerrorsfound")}
</span>
}
>
{problemJobs.map((problem) => (
<Alert <Alert
key={problem.id} key={problem.id}
type="error" type="error"
@@ -64,6 +72,20 @@ export function ScheduleCalendarWrapperComponent({
})} })}
/> />
))} ))}
</Collapse.Panel>
</Collapse>
) : (
problemJobs.map((problem) => (
<Alert
key={problem.id}
type="error"
message={t("appointments.labels.dataconsistency", {
ro_number: problem.ro_number,
code: problem.code,
})}
/>
))
)}
<Calendar <Calendar
events={data} events={data}

View File

@@ -15,6 +15,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { calculateScheduleLoad } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
@@ -28,6 +29,7 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
calculateScheduleLoad: (endDate) => dispatch(calculateScheduleLoad(endDate)),
}); });
export function ScheduleJobModalComponent({ export function ScheduleJobModalComponent({
@@ -36,6 +38,7 @@ export function ScheduleJobModalComponent({
existingAppointments, existingAppointments,
lbrHrsData, lbrHrsData,
jobId, jobId,
calculateScheduleLoad,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -57,6 +60,7 @@ export function ScheduleJobModalComponent({
const handleDateBlur = () => { const handleDateBlur = () => {
const values = form.getFieldsValue(); const values = form.getFieldsValue();
if (lbrHrsData) { if (lbrHrsData) {
const totalHours = const totalHours =
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs +
@@ -130,7 +134,12 @@ export function ScheduleJobModalComponent({
className="imex-flex-row__margin" className="imex-flex-row__margin"
key={idx} key={idx}
onClick={() => { 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(); handleDateBlur();
}} }}
> >
@@ -191,6 +200,9 @@ export function ScheduleJobModalComponent({
<Form.Item shouldUpdate={(prev, cur) => prev.start !== cur.start}> <Form.Item shouldUpdate={(prev, cur) => prev.start !== cur.start}>
{() => { {() => {
const values = form.getFieldsValue(); const values = form.getFieldsValue();
if (values.start) {
calculateScheduleLoad(values.start);
}
return ( return (
<div className="schedule-job-modal"> <div className="schedule-job-modal">
<ScheduleDayViewContainer day={values.start} /> <ScheduleDayViewContainer day={values.start} />

View File

@@ -1,5 +1,6 @@
.schedule-job-modal { .schedule-job-modal {
height: 70vh; height: 70vh;
overflow-y: auto;
.rbc-calendar { .rbc-calendar {
.rbc-toolbar { .rbc-toolbar {
.rbc-btn-group { .rbc-btn-group {

View File

@@ -294,6 +294,12 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
where: { inproduction: { _eq: true }, suspended: { _eq: false } } where: { inproduction: { _eq: true }, suspended: { _eq: false } }
) { ) {
id id
actual_in
scheduled_in
actual_completion
scheduled_completion
inproduction
ro_number
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) { ) {
@@ -327,12 +333,15 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
} }
) { ) {
id id
status
ro_number ro_number
scheduled_completion scheduled_completion
actual_completion actual_completion
scheduled_in
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
inproduction
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) { ) {
@@ -360,11 +369,16 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
) { ) {
id id
scheduled_in scheduled_in
actual_in
scheduled_completion
ro_number ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
alt_transport alt_transport
actual_completion
inproduction
status
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } } where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) { ) {

View File

@@ -37,7 +37,7 @@ export function* calculateScheduleLoad({ payload: end }) {
productionTotal: {}, productionTotal: {},
productionHours: 0, productionHours: 0,
}; };
let problemJobs = [];
//Set the current load. //Set the current load.
buckets.forEach((bucket) => { buckets.forEach((bucket) => {
load.productionTotal[bucket.id] = { count: 0, label: bucket.label }; load.productionTotal[bucket.id] = { count: 0, label: bucket.label };
@@ -45,6 +45,32 @@ export function* calculateScheduleLoad({ payload: end }) {
prodJobs.forEach((item) => { prodJobs.forEach((item) => {
//Add all of the jobs currently in production to the buckets so that we have a starting point. //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); const bucketId = CheckJobBucket(buckets, item);
load.productionHours = load.productionHours =
load.productionHours + load.productionHours +
@@ -59,77 +85,120 @@ export function* calculateScheduleLoad({ payload: end }) {
}); });
arrJobs.forEach((item) => { arrJobs.forEach((item) => {
if (!item.scheduled_in) if (!item.scheduled_in) {
console.log("JOB HAS NO SCHEDULED IN DATE.", item); 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]) { 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 =
(load[itemDate].hoursIn || 0) + (load[itemDate].hoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].jobsIn.push(item); }
} else { } else {
load[itemDate] = { load[itemDate] = {
jobsIn: [item], allJobsIn: [item],
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
jobsOut: [], jobsOut: [],
hoursIn: allJobsOut: [],
allHoursIn:
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.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) => { compJobs.forEach((item) => {
if (!(item.actual_completion || item.scheduled_completion)) 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 inProdJobs = prodJobs.find((p) => p.id === item.id);
const inArrJobs = arrJobs.find((p) => p.id === item.id); const inArrJobs = arrJobs.find((p) => p.id === item.id);
if (!(inProdJobs || inArrJobs)) { const AddJobForSchedulingCalc = 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 itemDate = moment( const itemDate = moment(
item.actual_completion || item.scheduled_completion item.actual_completion || item.scheduled_completion
).format("yyyy-MM-DD"); ).format("yyyy-MM-DD");
//Skip it, it's already completed.
if (!!load[itemDate]) { 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 =
(load[itemDate].hoursOut || 0) + (load[itemDate].hoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].jobsOut.push(item); }
} else { } else {
load[itemDate] = { load[itemDate] = {
jobsOut: [item], allJobsOut: [item],
hoursOut: 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.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs, item.larhrs.aggregate.sum.mod_lb_hrs,
}; };
@@ -137,6 +206,7 @@ export function* calculateScheduleLoad({ payload: end }) {
}); });
//Propagate the expected load to each day. //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());
for (var day = 0; day < range; day++) { for (var day = 0; day < range; day++) {
const current = moment(today).add(day, "days").format("yyyy-MM-DD"); const current = moment(today).add(day, "days").format("yyyy-MM-DD");
@@ -146,6 +216,7 @@ export function* calculateScheduleLoad({ payload: end }) {
if (!!!load[current]) { if (!!!load[current]) {
load[current] = {}; load[current] = {};
} }
if (day === 0) { if (day === 0) {
//Starting on day 1. The load is current. //Starting on day 1. The load is current.
load[current].expectedLoad = CalculateLoad( load[current].expectedLoad = CalculateLoad(

View File

@@ -49,7 +49,7 @@
"blocked": "Blocked", "blocked": "Blocked",
"cancelledappointment": "Canceled appointment for: ", "cancelledappointment": "Canceled appointment for: ",
"completingjobs": "Completing Jobs", "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: ", "expectedjobs": "Expected Jobs in Production: ",
"expectedprodhrs": "Expected Production Hours:", "expectedprodhrs": "Expected Production Hours:",
"history": "History", "history": "History",
@@ -61,6 +61,7 @@
"priorappointments": "Previous Appointments", "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. ", "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: ", "scheduledfor": "Scheduled appointment for: ",
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.",
"smartscheduling": "Smart Scheduling", "smartscheduling": "Smart Scheduling",
"suggesteddates": "Suggested Dates" "suggesteddates": "Suggested Dates"
}, },
@@ -2425,6 +2426,9 @@
"export_payables": "Export Log - Payables", "export_payables": "Export Log - Payables",
"export_payments": "Export Log - Payments", "export_payments": "Export Log - Payments",
"export_receivables": "Export Log - Receivables", "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_csr": "Gross Sales by CSR",
"gsr_by_delivery_date": "Gross Sales by Delivery Date", "gsr_by_delivery_date": "Gross Sales by Delivery Date",
"gsr_by_estimator": "Gross Sales by Estimator", "gsr_by_estimator": "Gross Sales by Estimator",

View File

@@ -61,6 +61,7 @@
"priorappointments": "Nombramientos previos", "priorappointments": "Nombramientos previos",
"reminder": "", "reminder": "",
"scheduledfor": "Cita programada para:", "scheduledfor": "Cita programada para:",
"severalerrorsfound": "",
"smartscheduling": "", "smartscheduling": "",
"suggesteddates": "" "suggesteddates": ""
}, },
@@ -2425,6 +2426,9 @@
"export_payables": "", "export_payables": "",
"export_payments": "", "export_payments": "",
"export_receivables": "", "export_receivables": "",
"gsr_by_atp": "",
"gsr_by_ats": "",
"gsr_by_category": "",
"gsr_by_csr": "", "gsr_by_csr": "",
"gsr_by_delivery_date": "", "gsr_by_delivery_date": "",
"gsr_by_estimator": "", "gsr_by_estimator": "",

View File

@@ -61,6 +61,7 @@
"priorappointments": "Rendez-vous précédents", "priorappointments": "Rendez-vous précédents",
"reminder": "", "reminder": "",
"scheduledfor": "Rendez-vous prévu pour:", "scheduledfor": "Rendez-vous prévu pour:",
"severalerrorsfound": "",
"smartscheduling": "", "smartscheduling": "",
"suggesteddates": "" "suggesteddates": ""
}, },
@@ -2425,6 +2426,9 @@
"export_payables": "", "export_payables": "",
"export_payments": "", "export_payments": "",
"export_receivables": "", "export_receivables": "",
"gsr_by_atp": "",
"gsr_by_ats": "",
"gsr_by_category": "",
"gsr_by_csr": "", "gsr_by_csr": "",
"gsr_by_delivery_date": "", "gsr_by_delivery_date": "",
"gsr_by_estimator": "", "gsr_by_estimator": "",

View File

@@ -1343,6 +1343,32 @@ export const TemplateList = (type, context) => {
}, },
group: "sales", 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: { gsr_labor_only: {
title: i18n.t("reportcenter.templates.gsr_labor_only"), title: i18n.t("reportcenter.templates.gsr_labor_only"),
description: "", description: "",

View File

@@ -23,6 +23,7 @@ let transporter = nodemailer.createTransport({
}); });
exports.sendServerEmail = async function ({ subject, text }) { exports.sendServerEmail = async function ({ subject, text }) {
if (process.env.NODE_ENV === undefined) return;
try { try {
transporter.sendMail( transporter.sendMail(
{ {

View File

@@ -536,7 +536,16 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
arrJobs: jobs(where: {scheduled_in: {_gte: $now}, suspended: {_eq: false}}) { arrJobs: jobs(where: {scheduled_in: {_gte: $now}, suspended: {_eq: false}}) {
id id
scheduled_in scheduled_in
actual_in
scheduled_completion
ro_number 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}}) { labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
aggregate { aggregate {
sum { 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}}]}]}) { compJobs: jobs(where: {_and: [{suspended: {_eq: false}}, {_or: [{scheduled_completion: {_gte: $now}}, {actual_completion: {_gte: $now}}]}]}) {
id id
status
ro_number ro_number
scheduled_completion scheduled_completion
actual_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}}) { labhrs: joblines_aggregate(where: {mod_lbr_ty: {_neq: "LAR"}, removed: {_eq: false}}) {
aggregate { aggregate {
sum { sum {
@@ -574,15 +589,20 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
} }
prodJobs: jobs(where: {inproduction: {_eq: true}, suspended: {_eq: false}}) { prodJobs: jobs(where: {inproduction: {_eq: true}, suspended: {_eq: false}}) {
id id
actual_in
scheduled_in
actual_completion
scheduled_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 { aggregate {
sum { sum {
mod_lb_hrs 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 { aggregate {
sum { sum {
mod_lb_hrs 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!) { exports.QUERY_EMPLOYEE_PIN = `query QUERY_EMPLOYEE_PIN($shopId: uuid!, $employeeId: String!) {
employees(where: {_and: {shopid: {_eq: $shopId}, employee_number: {_eq: $employeeId}}}) { employees(where: {_and: {shopid: {_eq: $shopId}, employee_number: {_eq: $employeeId}}}) {

View File

@@ -41,9 +41,9 @@ exports.job = async (req, res) => {
(bucket) => (bucket) =>
bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true) bucket.gte <= jobHrs && (!!bucket.lt ? bucket.lt > jobHrs : true)
)[0]; )[0];
const load = { const load = {
productionTotal: {}, productionTotal: {},
productionHours: 0,
}; };
//Set the current load. //Set the current load.
ssbuckets.forEach((bucket) => { ssbuckets.forEach((bucket) => {
@@ -70,27 +70,6 @@ exports.job = async (req, res) => {
// ); // );
const filteredArrJobs = []; 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) => { arrJobs.forEach((item) => {
let isSameBucket = false; let isSameBucket = false;
if (JobBucket.id === CheckJobBucket(ssbuckets, item)) { if (JobBucket.id === CheckJobBucket(ssbuckets, item)) {
@@ -102,21 +81,27 @@ exports.job = async (req, res) => {
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.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) .tz(timezone)
.format("yyyy-MM-DD"); .format("yyyy-MM-DD");
if (isSameBucket) { if (isSameBucket) {
if (!!load[itemDate]) { if (!!load[itemDate]) {
load[itemDate].hoursIn = (load[itemDate].hoursIn || 0) + jobHours; load[itemDate].hoursIn =
load[itemDate].jobsIn.push(item); (load[itemDate].hoursIn || 0) + AddJobForSchedulingCalc
? jobHours
: 0;
if (AddJobForSchedulingCalc) load[itemDate].jobsIn.push(item);
} else { } else {
load[itemDate] = { load[itemDate] = {
jobsIn: [item], jobsIn: AddJobForSchedulingCalc ? [item] : [],
jobsOut: [], jobsOut: [],
hoursIn: jobHours, hoursIn: AddJobForSchedulingCalc ? jobHours : 0,
}; };
} }
} }
if (!load[itemDate]) { if (!load[itemDate]) {
load[itemDate] = { load[itemDate] = {
jobsIn: [], jobsIn: [],
@@ -139,33 +124,8 @@ exports.job = async (req, res) => {
const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id); const inProdJobs = filteredProdJobsList.find((p) => p.id === item.id);
const inArrJobs = filteredArrJobs.find((p) => p.id === item.id); const inArrJobs = filteredArrJobs.find((p) => p.id === item.id);
if (!(inProdJobs || inArrJobs)) { const AddJobForSchedulingCalc = 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;
}
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;
} else {
const itemDate = moment( const itemDate = moment(
item.actual_completion || item.scheduled_completion item.actual_completion || item.scheduled_completion
) )
@@ -173,19 +133,20 @@ exports.job = async (req, res) => {
.format("yyyy-MM-DD"); .format("yyyy-MM-DD");
if (!!load[itemDate]) { if (!!load[itemDate]) {
load[itemDate].hoursOut = load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) + (load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc
item.labhrs.aggregate.sum.mod_lb_hrs + ? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs
load[itemDate].jobsOut.push(item); : 0;
if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item);
} else { } else {
load[itemDate] = { load[itemDate] = {
jobsOut: [item], jobsOut: AddJobForSchedulingCalc ? [item] : [],
hoursOut: hoursOut: AddJobForSchedulingCalc
item.labhrs.aggregate.sum.mod_lb_hrs + ? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs, item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
}; };
} }
}
}); });
//Propagate the expected load to each day. //Propagate the expected load to each day.