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

release/2023-02-03

Approved-by: Patrick Fic
This commit is contained in:
Patrick Fic
2023-02-02 18:10:12 +00:00
10 changed files with 211 additions and 55 deletions

View File

@@ -40880,6 +40880,48 @@
</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_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

@@ -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,119 @@ 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 &&
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 &&
!moment(item.actual_completion || item.scheduled_completion).isSame(
moment(item.actual_in || item.scheduled_in),
"day"
);
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 =
//Job isn't found in production or coming in. item.inproduction &&
//is it going today or scheduled to go today? !moment(item.actual_completion || item.scheduled_completion).isSame(
if ( moment(item.scheduled_in),
moment(item.actual_completion || item.scheduled_completion).isSame( "day"
moment(), ) &&
"day" (inProdJobs || inArrJobs);
)
) {
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 +205,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 +215,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",
@@ -2425,6 +2425,8 @@
"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": "Gross Sales by ATP",
"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

@@ -2425,6 +2425,8 @@
"export_payables": "", "export_payables": "",
"export_payments": "", "export_payments": "",
"export_receivables": "", "export_receivables": "",
"gsr_by_atp": "",
"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

@@ -2425,6 +2425,8 @@
"export_payables": "", "export_payables": "",
"export_payments": "", "export_payments": "",
"export_receivables": "", "export_receivables": "",
"gsr_by_atp": "",
"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_atp: {
title: i18n.t("reportcenter.templates.gsr_by_atp"),
description: "",
subject: i18n.t("reportcenter.templates.gsr_by_atp"),
key: "gsr_by_atp",
//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(
{ {