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,7 +53,28 @@ export function ScheduleCalendarWrapperComponent({
return ( return (
<> <>
<JobDetailCards /> <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) => ( problemJobs.map((problem) => (
<Alert <Alert
key={problem.id} key={problem.id}
@@ -63,7 +84,8 @@ export function ScheduleCalendarWrapperComponent({
code: problem.code, 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].hoursIn = load[itemDate].allHoursIn =
(load[itemDate].hoursIn || 0) + (load[itemDate].allHoursIn || 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);
//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 { } 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].hoursOut = load[itemDate].allHoursOut =
(load[itemDate].hoursOut || 0) + (load[itemDate].allHoursOut || 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); //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 { } 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,52 +124,28 @@ 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 ( const itemDate = moment(
moment(item.actual_completion || item.scheduled_completion) item.actual_completion || item.scheduled_completion
.tz(timezone) )
.isBefore(moment().tz(timezone), "day") .tz(timezone)
) { .format("yyyy-MM-DD");
console.log("Job should have already gone. Ignoring it.", item); if (!!load[itemDate]) {
return; load[itemDate].hoursOut =
} (load[itemDate].hoursOut || 0) + AddJobForSchedulingCalc
console.log("PROBLEM JOB", item); ? item.labhrs.aggregate.sum.mod_lb_hrs +
problemJobs.push({ item.larhrs.aggregate.sum.mod_lb_hrs
...item, : 0;
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", if (AddJobForSchedulingCalc) load[itemDate].jobsOut.push(item);
});
return;
} else { } else {
const itemDate = moment( load[itemDate] = {
item.actual_completion || item.scheduled_completion jobsOut: AddJobForSchedulingCalc ? [item] : [],
) hoursOut: AddJobForSchedulingCalc
.tz(timezone) ? item.labhrs.aggregate.sum.mod_lb_hrs +
.format("yyyy-MM-DD"); item.larhrs.aggregate.sum.mod_lb_hrs
if (!!load[itemDate]) { : 0,
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,
};
}
} }
}); });