Merge branch 'release/2023-06-09' into feature/IO-2278-parts-dispatching

This commit is contained in:
Patrick Fic
2023-06-06 13:20:10 -07:00
16 changed files with 239 additions and 88 deletions

View File

@@ -145,7 +145,7 @@
//Update row highlighting on production board. //Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td { .ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important; background: #e7f3ff !important;
} }
.job-line-manual { .job-line-manual {
@@ -156,3 +156,11 @@
td.ant-table-column-sort { td.ant-table-column-sort {
background-color: transparent; background-color: transparent;
} }
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
background-color: #f4f4f4;
}
.rowWithColor > td {
background-color: var(--bgColor) !important;
}

View File

@@ -1,28 +1,28 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons"; import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import { import {
Button,
Divider, Divider,
Dropdown,
Form, Form,
Input, Input,
Menu,
Select, Select,
Space,
Tabs, Tabs,
Upload, Upload,
Space,
Menu,
Dropdown,
Button,
} from "antd"; } from "antd";
import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
import _ from "lodash";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia"; import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import { selectEmailConfig } from "../../redux/email/email.selectors"; import EmailDocumentsComponent from "../email-documents/email-documents.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -54,6 +54,15 @@ export function EmailOverlayComponent({
]), ]),
}); });
}; };
const handle_CC_Click = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({
cc: _.uniq([
...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email),
]),
});
};
const menu = ( const menu = (
<div> <div>
@@ -74,6 +83,25 @@ export function EmailOverlayComponent({
</div> </div>
); );
const menuCC = (
<div>
<Menu onClick={handle_CC_Click}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
{bodyshop.md_to_emails.map((e, idx) => (
<Menu.Item value={e.emails} key={idx + "group"}>
{e.label}
</Menu.Item>
))}
</Menu>
</div>
);
return ( return (
<div> <div>
<Form.Item <Form.Item
@@ -122,7 +150,23 @@ export function EmailOverlayComponent({
> >
<Select mode="tags" tokenSeparators={[",", ";"]} /> <Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item> </Form.Item>
<Form.Item label={t("emails.fields.cc")} name="cc"> <Form.Item
label={
<Space>
{t("emails.fields.cc")}
<Dropdown overlay={menuCC}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="cc"
>
<Select mode="tags" tokenSeparators={[",", ";"]} /> <Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item

View File

@@ -29,6 +29,7 @@ import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component"; import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util"; import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component"; import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -461,54 +462,56 @@ export function JobsDetailHeaderActions({
)} )}
<JobsDetailHeaderActionsAddevent jobid={job.id} /> <JobsDetailHeaderActionsAddevent jobid={job.id} />
{!jobRO && job.converted && ( {!jobRO && job.converted && (
<Menu.Item> <RbacWrapper action="jobs:void" noauth>
<Popconfirm <Menu.Item>
title={t("jobs.labels.voidjob")} <Popconfirm
okText="Yes" title={t("jobs.labels.voidjob")}
cancelText="No" okText="Yes"
onClick={(e) => e.stopPropagation()} cancelText="No"
onConfirm={async () => { onClick={(e) => e.stopPropagation()}
//delete the job. onConfirm={async () => {
const result = await voidJob({ //delete the job.
variables: { const result = await voidJob({
jobId: job.id, variables: {
job: { jobId: job.id,
status: bodyshop.md_ro_statuses.default_void, job: {
voided: true, status: bodyshop.md_ro_statuses.default_void,
scheduled_in: null, voided: true,
scheduled_completion: null, scheduled_in: null,
inproduction: false, scheduled_completion: null,
}, inproduction: false,
note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
}, },
], note: [
}, {
}); jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
},
],
},
});
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.voided"), message: t("jobs.successes.voided"),
}); });
//go back to jobs list. //go back to jobs list.
history.push(`/manage/`); history.push(`/manage/`);
} else { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.voiding", { message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors), error: JSON.stringify(result.errors),
}), }),
}); });
} }
}} }}
getPopupContainer={(trigger) => trigger.parentNode} getPopupContainer={(trigger) => trigger.parentNode}
> >
{t("menus.jobsactions.void")} {t("menus.jobsactions.void")}
</Popconfirm> </Popconfirm>
</Menu.Item> </Menu.Item>
</RbacWrapper>
)} )}
</Menu> </Menu>
); );

View File

@@ -112,7 +112,9 @@ export function JobsList({ bodyshop }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), sorter: (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,

View File

@@ -23,17 +23,34 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const { id: jobId, job } = printCenterModal.context; const { id: jobId, job } = printCenterModal.context;
const tempList = TemplateList("job", {}); const tempList = TemplateList("job", {});
const { t } = useTranslation(); const { t } = useTranslation();
const JobsReportsList = Object.keys(tempList)
.map((key) => { const JobsReportsList =
return tempList[key]; bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
}) ? Object.keys(tempList)
.filter( .map((key) => {
(temp) => return tempList[key];
!temp.regions || })
(temp.regions && temp.regions[bodyshop.region_config]) || .filter(
(temp.regions && (temp) =>
bodyshop.region_config.includes(Object.keys(temp.regions)) === true) (!temp.regions ||
); (temp.regions && temp.regions[bodyshop.region_config]) ||
(temp.regions &&
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
true)) &&
(!temp.dms || temp.dms === false)
)
: Object.keys(tempList)
.map((key) => {
return tempList[key];
})
.filter(
(temp) =>
!temp.regions ||
(temp.regions && temp.regions[bodyshop.region_config]) ||
(temp.regions &&
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
true)
);
const filteredJobsReportsList = const filteredJobsReportsList =
search !== "" search !== ""

View File

@@ -246,11 +246,21 @@ export function ProductionListTable({
(x) => x.status === record.status (x) => x.status === record.status
); );
if (!color) return null; if (!color) {
if (index % 2 === 0)
return {
style: {
backgroundColor: `rgb(236, 236, 236)`,
},
};
return null;
}
return { return {
className: "rowWithColor",
style: { style: {
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`, "--bgColor": `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
}, },
}; };
}, },

View File

@@ -26,6 +26,8 @@ const ret = {
"jobs:partsqueue": 4, "jobs:partsqueue": 4,
"jobs:checklist-view": 2, "jobs:checklist-view": 2,
"jobs:list-ready": 1, "jobs:list-ready": 1,
"jobs:void": 5,
"bills:enter": 2, "bills:enter": 2,
"bills:view": 2, "bills:view": 2,
"bills:list": 2, "bills:list": 2,

View File

@@ -12,7 +12,8 @@ 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, Collapse } from "antd"; import { Alert, Collapse } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation, Trans } from "react-i18next";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -66,10 +67,21 @@ export function ScheduleCalendarWrapperComponent({
<Alert <Alert
key={problem.id} key={problem.id}
type="error" type="error"
message={t("appointments.labels.dataconsistency", { message={
ro_number: problem.ro_number, <Trans
code: problem.code, i18nKey="appointments.labels.dataconsistency"
})} components={[
<Link
to={`/manage/jobs/${problem.id}`}
target="_blank"
/>,
]}
values={{
ro_number: problem.ro_number,
code: problem.code,
}}
/>
}
/> />
))} ))}
</Collapse.Panel> </Collapse.Panel>
@@ -79,10 +91,18 @@ export function ScheduleCalendarWrapperComponent({
<Alert <Alert
key={problem.id} key={problem.id}
type="error" type="error"
message={t("appointments.labels.dataconsistency", { message={
ro_number: problem.ro_number, <Trans
code: problem.code, i18nKey="appointments.labels.dataconsistency"
})} components={[
<Link to={`/manage/jobs/${problem.id}`} target="_blank" />,
]}
values={{
ro_number: problem.ro_number,
code: problem.code,
}}
/>
}
/> />
)) ))
)} )}

View File

@@ -1,12 +1,12 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { Form, InputNumber } from "antd"; import { Form, InputNumber } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import { useTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -316,6 +316,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.void")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:void"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.bills.enter")} label={t("bodyshop.fields.rbac.bills.enter")}
rules={[ rules={[

View File

@@ -33,6 +33,7 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
<JobSearchSelect <JobSearchSelect
convertedOnly={!bodyshop.tt_allow_post_to_invoiced} convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
notExported={!bodyshop.tt_allow_post_to_invoiced} notExported={!bodyshop.tt_allow_post_to_invoiced}
notInvoiced={!bodyshop.tt_allow_post_to_invoiced}
/> />
</Form.Item> </Form.Item>

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 may have been excluded for scheduling purposes. CODE: {{code}}.", "dataconsistency": "<0>{{ro_number}}</0> 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",
@@ -405,7 +405,8 @@
"list-active": "Jobs -> List Active", "list-active": "Jobs -> List Active",
"list-all": "Jobs -> List All", "list-all": "Jobs -> List All",
"list-ready": "Jobs -> List Ready", "list-ready": "Jobs -> List Ready",
"partsqueue": "Jobs -> Parts Queue" "partsqueue": "Jobs -> Parts Queue",
"void": "Jobs -> Void"
}, },
"owners": { "owners": {
"detail": "Owners -> Detail", "detail": "Owners -> Detail",

View File

@@ -405,7 +405,8 @@
"list-active": "", "list-active": "",
"list-all": "", "list-all": "",
"list-ready": "", "list-ready": "",
"partsqueue": "" "partsqueue": "",
"void": ""
}, },
"owners": { "owners": {
"detail": "", "detail": "",

View File

@@ -405,7 +405,8 @@
"list-active": "", "list-active": "",
"list-all": "", "list-all": "",
"list-ready": "", "list-ready": "",
"partsqueue": "" "partsqueue": "",
"void": ""
}, },
"owners": { "owners": {
"detail": "", "detail": "",

View File

@@ -512,6 +512,7 @@ export const TemplateList = (type, context) => {
key: "dms_posting_sheet", key: "dms_posting_sheet",
disabled: false, disabled: false,
group: "financial", group: "financial",
dms: true,
}, },
} }
: {}), : {}),

View File

@@ -2690,6 +2690,13 @@
table: table:
name: inventory name: inventory
schema: public schema: public
- name: parts_dispatch_lines
using:
foreign_key_constraint_on:
column: joblineid
table:
name: parts_dispatch_lines
schema: public
- name: parts_order_lines - name: parts_order_lines
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -3131,6 +3138,13 @@
table: table:
name: notes name: notes
schema: public schema: public
- name: parts_dispatches
using:
foreign_key_constraint_on:
column: jobid
table:
name: parts_dispatch
schema: public
- name: parts_orders - name: parts_orders
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -4559,9 +4573,17 @@
- table: - table:
name: parts_dispatch name: parts_dispatch
schema: public schema: public
object_relationships:
- name: job
using:
foreign_key_constraint_on: jobid
- table: - table:
name: parts_dispatch_lines name: parts_dispatch_lines
schema: public schema: public
object_relationships:
- name: jobline
using:
foreign_key_constraint_on: joblineid
- table: - table:
name: parts_order_lines name: parts_order_lines
schema: public schema: public

View File

@@ -729,9 +729,15 @@ async function InsertDmsVehicle(socket) {
deliveryDate: moment() deliveryDate: moment()
// .tz(socket.JobData.bodyshop.timezone) // .tz(socket.JobData.bodyshop.timezone)
.format("YYYYMMDD"), .format("YYYYMMDD"),
licensePlateNo: String(socket.JobData.plate_no) licensePlateNo:
.replace(/([^\w]|_)/g, "") socket.JobData.plate_no === null
.toUpperCase(), ? null
: String(socket.JobData.plate_no).replace(/([^\w]|_)/g, "")
.length === 0
? null
: String(socket.JobData.plate_no)
.replace(/([^\w]|_)/g, "")
.toUpperCase(),
make: socket.txEnvelope.dms_make, make: socket.txEnvelope.dms_make,
modelAbrev: socket.txEnvelope.dms_model, modelAbrev: socket.txEnvelope.dms_model,
modelYear: socket.JobData.v_model_yr, modelYear: socket.JobData.v_model_yr,