Merge branch 'release/2023-01-27' into feature/america

This commit is contained in:
Patrick Fic
2023-01-27 15:33:59 -08:00
15 changed files with 245 additions and 47 deletions

View File

@@ -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
@@ -736,6 +736,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>expectedjobs</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>expectedprodhrs</name> <name>expectedprodhrs</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -41489,6 +41510,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>jobs_completed_not_invoiced</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>jobs_invoiced_not_exported</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>jobs_reconcile</name> <name>jobs_reconcile</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -41615,6 +41678,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>open_orders_specific_csr</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>open_orders_status</name> <name>open_orders_status</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -42224,6 +42308,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>scheduled_parts_list</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>scoreboard_detail</name> <name>scoreboard_detail</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,6 +1,5 @@
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { LoadingOutlined } from "@ant-design/icons"; import { AutoComplete, Divider, Input, Space } from "antd";
import { AutoComplete, Divider, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -9,7 +8,7 @@ import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, { import OwnerNameDisplay, {
OwnerNameDisplayFunction, OwnerNameDisplayFunction
} from "../owner-name-display/owner-name-display.component"; } from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() { export default function GlobalSearch() {
@@ -178,13 +177,18 @@ export default function GlobalSearch() {
<AutoComplete <AutoComplete
options={options} options={options}
onSearch={handleSearch} onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")}
allowClear
onSelect={(val, opt) => { onSelect={(val, opt) => {
history.push(opt.label.props.to); history.push(opt.label.props.to);
}} }}
></AutoComplete> >
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
); );
} }

View File

@@ -166,16 +166,16 @@ export function JobLinesUpsertModalComponent({
name="ah_detail_line" name="ah_detail_line"
valuePropName="checked" valuePropName="checked"
dependencies={["mod_lbr_ty"]} dependencies={["mod_lbr_ty"]}
initialValue={false}
rules={[ rules={[
({ getFieldValue }) => ({ ({ getFieldValue }) => ({
validator(rule, value) { validator(rule, value) {
console.log( if (
value === true, value === false ||
["LA1", "LA2", "LA3", "LA4", "LAU"].includes( value === undefined ||
getFieldValue("mod_lbr_ty") value === null
) )
); return Promise.resolve();
if (value === false) return Promise.resolve();
if ( if (
value === true && value === true &&
["LA1", "LA2", "LA3", "LA4", "LAU"].includes( ["LA1", "LA2", "LA3", "LA4", "LAU"].includes(

View File

@@ -43,14 +43,21 @@ export function JobsConvertButton({
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const handleConvert = async (values) => { const handleConvert = async ({ employee_csr, ...values }) => {
if (parentFormIsFieldsTouched()) { if (parentFormIsFieldsTouched()) {
alert(t("jobs.labels.savebeforeconversion")); alert(t("jobs.labels.savebeforeconversion"));
return; return;
} }
setLoading(true); setLoading(true);
const res = await mutationConvertJob({ const res = await mutationConvertJob({
variables: { jobId: job.id, ...values }, variables: {
jobId: job.id,
job: {
converted: true,
...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}),
...values,
},
},
}); });
if (values.ca_gst_registrant) { if (values.ca_gst_registrant) {

View File

@@ -4,11 +4,13 @@ import React, { useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { import {
Legend, PolarAngleAxis, Legend,
PolarAngleAxis,
PolarGrid, PolarGrid,
PolarRadiusAxis, PolarRadiusAxis,
Radar, RadarChart, Radar,
Tooltip RadarChart,
Tooltip,
} from "recharts"; } from "recharts";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
@@ -20,6 +22,10 @@ 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(() => {
@@ -44,6 +50,8 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
<Space> <Space>
{t("appointments.labels.expectedprodhrs")} {t("appointments.labels.expectedprodhrs")}
<strong>{loadData?.expectedHours?.toFixed(1)}</strong> <strong>{loadData?.expectedHours?.toFixed(1)}</strong>
{t("appointments.labels.expectedjobs")}
<strong>{loadData?.expectedJobCount}</strong>
</Space> </Space>
<RadarChart <RadarChart
// cx={300} // cx={300}

View File

@@ -85,7 +85,7 @@ export function TimeTicketList({
text: (() => { text: (() => {
const emp = bodyshop.employees.find((e) => e.id === s); const emp = bodyshop.employees.find((e) => e.id === s);
return `${emp.first_name} ${emp.last_name}`; return `${emp?.first_name} ${emp?.last_name}`;
})(), // })(), //
value: [s], value: [s],
}; };

View File

@@ -131,6 +131,7 @@ const JobRelatedTicketsTable = ({
return { return {
id: `${item.jobKey}${costCenter}`, id: `${item.jobKey}${costCenter}`,
costCenter,
item, item,
actHrs: actHrs.toFixed(1), actHrs: actHrs.toFixed(1),
prodHrs: prodHrs.toFixed(1), prodHrs: prodHrs.toFixed(1),
@@ -151,7 +152,9 @@ const JobRelatedTicketsTable = ({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "empname" && state.sortedInfo.order, state.sortedInfo.columnKey === "empname" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
`${record.item.employee.first_name} ${record.item.employee.last_name}`, `${record.item.employee.first_name} ${record.item.employee.last_name} ${
record.costCenter ? `(${record.costCenter})` : ""
}`.trim(),
}, },
{ {
title: t("timetickets.fields.actualhrs"), title: t("timetickets.fields.actualhrs"),

View File

@@ -1153,31 +1153,8 @@ export const UPDATE_JOBS = gql`
`; `;
export const CONVERT_JOB_TO_RO = gql` export const CONVERT_JOB_TO_RO = gql`
mutation CONVERT_JOB_TO_RO( mutation CONVERT_JOB_TO_RO($jobId: uuid!, $job: jobs_set_input!) {
$jobId: uuid! update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
$class: String
$ins_co_nm: String!
$ca_gst_registrant: Boolean
$driveable: Boolean
$towin: Boolean
$referral_source: String
$referral_source_extra: String
$employee_csr: uuid
) {
update_jobs(
where: { id: { _eq: $jobId } }
_set: {
converted: true
ins_co_nm: $ins_co_nm
class: $class
ca_gst_registrant: $ca_gst_registrant
towin: $towin
driveable: $driveable
referral_source: $referral_source
referral_source_extra: $referral_source_extra
employee_csr: $employee_csr
}
) {
returning { returning {
id id
ro_number ro_number

View File

@@ -154,6 +154,10 @@ export function* calculateScheduleLoad({ payload: end }) {
load[current].jobsIn || [], load[current].jobsIn || [],
load[current].jobsOut || [] load[current].jobsOut || []
); );
load[current].expectedJobCount =
prodJobs.length +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours = load[current].expectedHours =
load.productionHours + load.productionHours +
(load[current].hoursIn || 0) - (load[current].hoursIn || 0) -
@@ -165,6 +169,10 @@ export function* calculateScheduleLoad({ payload: end }) {
load[current].jobsIn || [], load[current].jobsIn || [],
load[current].jobsOut || [] load[current].jobsOut || []
); );
load[current].expectedJobCount =
load[prev].expectedJobCount +
(load[current].jobsIn || []).length -
(load[current].jobsOut || []).length;
load[current].expectedHours = load[current].expectedHours =
load[prev].expectedHours + load[prev].expectedHours +
(load[current].hoursIn || 0) - (load[current].hoursIn || 0) -

View File

@@ -50,6 +50,7 @@
"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 has been excluded for scheduling purposes. CODE: {{code}}.",
"expectedjobs": "Expected Jobs in Production: ",
"expectedprodhrs": "Expected Production Hours:", "expectedprodhrs": "Expected Production Hours:",
"history": "History", "history": "History",
"inproduction": "Jobs In Production", "inproduction": "Jobs In Production",
@@ -2454,12 +2455,15 @@
"job_costing_ro_date_summary": "Job Costing by RO - Summary", "job_costing_ro_date_summary": "Job Costing by RO - Summary",
"job_costing_ro_estimator": "Job Costing by Estimator", "job_costing_ro_estimator": "Job Costing by Estimator",
"job_costing_ro_ins_co": "Job Costing by RO Source", "job_costing_ro_ins_co": "Job Costing by RO Source",
"jobs_completed_not_invoiced": "Jobs Completed not Invoiced",
"jobs_invoiced_not_exported": "Jobs Invoiced not Exported",
"jobs_reconcile": "Parts/Sublet/Labor Reconciliation", "jobs_reconcile": "Parts/Sublet/Labor Reconciliation",
"lag_time": "Lag Time", "lag_time": "Lag Time",
"open_orders": "Open Orders by Date", "open_orders": "Open Orders by Date",
"open_orders_csr": "Open Orders by CSR", "open_orders_csr": "Open Orders by CSR",
"open_orders_estimator": "Open Orders by Estimator", "open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company", "open_orders_ins_co": "Open Orders by Insurance Company",
"open_orders_specific_csr": "Open Orders filtered by CSR",
"open_orders_status": "Open Orders by Status", "open_orders_status": "Open Orders by Status",
"parts_backorder": "IOU Parts List", "parts_backorder": "IOU Parts List",
"parts_not_recieved": "Parts Not Received", "parts_not_recieved": "Parts Not Received",
@@ -2489,6 +2493,7 @@
"returns_grouped_by_vendor_detailed": "Returns Grouped by Vendor - Detailed", "returns_grouped_by_vendor_detailed": "Returns Grouped by Vendor - Detailed",
"returns_grouped_by_vendor_summary": "Returns Grouped by Vendor - Summary", "returns_grouped_by_vendor_summary": "Returns Grouped by Vendor - Summary",
"schedule": "Appointment Schedule", "schedule": "Appointment Schedule",
"scheduled_parts_list": "Parts for Jobs Scheduled In",
"scoreboard_detail": "Scoreboard Detail", "scoreboard_detail": "Scoreboard Detail",
"scoreboard_summary": "Scoreboard Summary", "scoreboard_summary": "Scoreboard Summary",
"supplement_ratio_ins_co": "Supplement Ratio by Source", "supplement_ratio_ins_co": "Supplement Ratio by Source",

View File

@@ -50,6 +50,7 @@
"cancelledappointment": "Cita cancelada para:", "cancelledappointment": "Cita cancelada para:",
"completingjobs": "", "completingjobs": "",
"dataconsistency": "", "dataconsistency": "",
"expectedjobs": "",
"expectedprodhrs": "", "expectedprodhrs": "",
"history": "", "history": "",
"inproduction": "", "inproduction": "",
@@ -2454,12 +2455,15 @@
"job_costing_ro_date_summary": "", "job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "", "job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "", "job_costing_ro_ins_co": "",
"jobs_completed_not_invoiced": "",
"jobs_invoiced_not_exported": "",
"jobs_reconcile": "", "jobs_reconcile": "",
"lag_time": "", "lag_time": "",
"open_orders": "", "open_orders": "",
"open_orders_csr": "", "open_orders_csr": "",
"open_orders_estimator": "", "open_orders_estimator": "",
"open_orders_ins_co": "", "open_orders_ins_co": "",
"open_orders_specific_csr": "",
"open_orders_status": "", "open_orders_status": "",
"parts_backorder": "", "parts_backorder": "",
"parts_not_recieved": "", "parts_not_recieved": "",
@@ -2489,6 +2493,7 @@
"returns_grouped_by_vendor_detailed": "", "returns_grouped_by_vendor_detailed": "",
"returns_grouped_by_vendor_summary": "", "returns_grouped_by_vendor_summary": "",
"schedule": "", "schedule": "",
"scheduled_parts_list": "",
"scoreboard_detail": "", "scoreboard_detail": "",
"scoreboard_summary": "", "scoreboard_summary": "",
"supplement_ratio_ins_co": "", "supplement_ratio_ins_co": "",

View File

@@ -50,6 +50,7 @@
"cancelledappointment": "Rendez-vous annulé pour:", "cancelledappointment": "Rendez-vous annulé pour:",
"completingjobs": "", "completingjobs": "",
"dataconsistency": "", "dataconsistency": "",
"expectedjobs": "",
"expectedprodhrs": "", "expectedprodhrs": "",
"history": "", "history": "",
"inproduction": "", "inproduction": "",
@@ -2454,12 +2455,15 @@
"job_costing_ro_date_summary": "", "job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "", "job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "", "job_costing_ro_ins_co": "",
"jobs_completed_not_invoiced": "",
"jobs_invoiced_not_exported": "",
"jobs_reconcile": "", "jobs_reconcile": "",
"lag_time": "", "lag_time": "",
"open_orders": "", "open_orders": "",
"open_orders_csr": "", "open_orders_csr": "",
"open_orders_estimator": "", "open_orders_estimator": "",
"open_orders_ins_co": "", "open_orders_ins_co": "",
"open_orders_specific_csr": "",
"open_orders_status": "", "open_orders_status": "",
"parts_backorder": "", "parts_backorder": "",
"parts_not_recieved": "", "parts_not_recieved": "",
@@ -2489,6 +2493,7 @@
"returns_grouped_by_vendor_detailed": "", "returns_grouped_by_vendor_detailed": "",
"returns_grouped_by_vendor_summary": "", "returns_grouped_by_vendor_summary": "",
"schedule": "", "schedule": "",
"scheduled_parts_list": "",
"scoreboard_detail": "", "scoreboard_detail": "",
"scoreboard_summary": "", "scoreboard_summary": "",
"supplement_ratio_ins_co": "", "supplement_ratio_ins_co": "",

View File

@@ -1421,6 +1421,19 @@ export const TemplateList = (type, context) => {
}, },
group: "jobs", group: "jobs",
}, },
open_orders_specific_csr: {
title: i18n.t("reportcenter.templates.open_orders_specific_csr"),
description: "",
subject: i18n.t("reportcenter.templates.open_orders_specific_csr"),
key: "open_orders_specific_csr",
idtype: "employee",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
group: "jobs",
},
export_payables: { export_payables: {
title: i18n.t("reportcenter.templates.export_payables"), title: i18n.t("reportcenter.templates.export_payables"),
description: "", description: "",
@@ -1734,6 +1747,46 @@ export const TemplateList = (type, context) => {
}, },
group: "jobs", group: "jobs",
}, },
scheduled_parts_list: {
title: i18n.t("reportcenter.templates.scheduled_parts_list"),
subject: i18n.t("reportcenter.templates.scheduled_parts_list"),
key: "scheduled_parts_list",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.scheduled_in"),
},
group: "jobs",
},
jobs_completed_not_invoiced: {
title: i18n.t("reportcenter.templates.jobs_completed_not_invoiced"),
subject: i18n.t(
"reportcenter.templates.jobs_completed_not_invoiced"
),
key: "jobs_completed_not_invoiced",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "jobs",
},
jobs_invoiced_not_exported: {
title: i18n.t("reportcenter.templates.jobs_invoiced_not_exported"),
subject: i18n.t(
"reportcenter.templates.jobs_invoiced_not_exported"
),
key: "jobs_invoiced_not_exported",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
group: "jobs",
},
} }
: {}), : {}),
...(!type || type === "courtesycarcontract" ...(!type || type === "courtesycarcontract"

View File

@@ -0,0 +1,10 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
-- SELECT j.jobid,
-- j.status,
-- count(1) AS count,
-- j.part_type
-- FROM joblines j
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.removed IS FALSE))
-- GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE VIEW "public"."joblines_status" AS
SELECT j.jobid,
j.status,
count(1) AS count,
j.part_type
FROM joblines j
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.removed IS FALSE))
GROUP BY j.jobid, j.status, j.part_type;