Merged in feature/IO-3596-Manual-Line-Lock-Down (pull request #3100)

IO-3596 Manual Line Lock Down

Approved-by: Dave Richer
This commit is contained in:
Allan Carr
2026-03-06 22:23:03 +00:00
committed by Dave Richer
6 changed files with 61 additions and 38 deletions

View File

@@ -33,7 +33,7 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
import _ from "lodash"; import _ from "lodash";
import { FaTasks } from "react-icons/fa"; import { FaTasks } from "react-icons/fa";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
@@ -49,6 +49,7 @@ import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import JobLinesExpanderSimple from "./jobs-lines-expander-simple.component"; import JobLinesExpanderSimple from "./jobs-lines-expander-simple.component";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx"; import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component.jsx";
const UPDATE_JOB_LINES_LOCATION_BULK = gql` const UPDATE_JOB_LINES_LOCATION_BULK = gql`
mutation UPDATE_JOB_LINES_LOCATION_BULK($ids: [uuid!]!, $location: String!) { mutation UPDATE_JOB_LINES_LOCATION_BULK($ids: [uuid!]!, $location: String!) {
@@ -66,7 +67,8 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
technician: selectTechnician, technician: selectTechnician,
isPartsEntry: selectIsPartsEntry isPartsEntry: selectIsPartsEntry,
authLevel: selectAuthLevel
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -94,7 +96,8 @@ export function JobLinesComponent({
setTaskUpsertContext, setTaskUpsertContext,
billsQuery, billsQuery,
handlePartsOrderOnRowClick, handlePartsOrderOnRowClick,
isPartsEntry isPartsEntry,
authLevel
}) { }) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
const [bulkUpdateLocations] = useMutation(UPDATE_JOB_LINES_LOCATION_BULK); const [bulkUpdateLocations] = useMutation(UPDATE_JOB_LINES_LOCATION_BULK);
@@ -386,18 +389,20 @@ export function JobLinesComponent({
key: "actions", key: "actions",
render: (text, record) => ( render: (text, record) => (
<Space> <Space>
{(record.manual_line || jobIsPrivate) && !technician && ( {(record.manual_line || jobIsPrivate) &&
<Button !technician &&
disabled={jobRO} HasRbacAccess({ bodyshop, authLevel, action: "jobs:manual-line" }) && (
onClick={() => { <Button
setJobLineEditContext({ disabled={jobRO}
actions: { refetch: refetch, submit: form && form.submit }, onClick={() => {
context: { ...record, jobid: job.id } setJobLineEditContext({
}); actions: { refetch: refetch, submit: form && form.submit },
}} context: { ...record, jobid: job.id }
icon={<EditFilled />} });
/> }}
)} icon={<EditFilled />}
/>
)}
<Button <Button
title={t("tasks.buttons.create")} title={t("tasks.buttons.create")}
onClick={() => { onClick={() => {
@@ -410,29 +415,30 @@ export function JobLinesComponent({
}} }}
icon={<FaTasks />} icon={<FaTasks />}
/> />
{(record.manual_line || jobIsPrivate) &&
{(record.manual_line || jobIsPrivate) && !technician && ( !technician &&
<Button HasRbacAccess({ bodyshop, authLevel, action: "jobs:manual-line" }) && (
disabled={jobRO} <Button
onClick={async () => { disabled={jobRO}
await deleteJobLine({ onClick={async () => {
variables: { joblineId: record.id }, await deleteJobLine({
update(cache) { variables: { joblineId: record.id },
cache.modify({ update(cache) {
fields: { cache.modify({
joblines(existingJobLines, { readField }) { fields: {
return existingJobLines.filter((jlRef) => record.id !== readField("id", jlRef)); joblines(existingJobLines, { readField }) {
return existingJobLines.filter((jlRef) => record.id !== readField("id", jlRef));
}
} }
} });
}); }
} });
}); await axios.post("/job/totalsssu", { id: job.id });
await axios.post("/job/totalsssu", { id: job.id }); if (refetch) refetch();
if (refetch) refetch(); }}
}} icon={<DeleteFilled />}
icon={<DeleteFilled />} />
/> )}
)}
</Space> </Space>
) )
} }
@@ -657,7 +663,7 @@ export function JobLinesComponent({
<Button id="repair-data-mark-button">{t("jobs.actions.mark")}</Button> <Button id="repair-data-mark-button">{t("jobs.actions.mark")}</Button>
</Dropdown> </Dropdown>
{!isPartsEntry && ( {!isPartsEntry && HasRbacAccess({ bodyshop, authLevel, action: "jobs:manual-line" }) && (
<Button <Button
disabled={jobRO || technician} disabled={jobRO || technician}
onClick={() => { onClick={() => {

View File

@@ -26,6 +26,7 @@ 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:manual-line": 1,
"jobs:void": 5, "jobs:void": 5,
"bills:enter": 2, "bills:enter": 2,

View File

@@ -435,6 +435,19 @@ export function ShopInfoRbacComponent({ bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item>, </Form.Item>,
<Form.Item
key="jobs:manual-line"
label={t("bodyshop.fields.rbac.jobs.manual-line")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "jobs:manual-line"]}
>
<InputNumber />
</Form.Item>,
<Form.Item <Form.Item
key="jobs:partsqueue" key="jobs:partsqueue"
label={t("bodyshop.fields.rbac.jobs.partsqueue")} label={t("bodyshop.fields.rbac.jobs.partsqueue")}

View File

@@ -519,6 +519,7 @@
"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",
"manual-line": "Jobs -> Manual Line",
"partsqueue": "Jobs -> Parts Queue", "partsqueue": "Jobs -> Parts Queue",
"void": "Jobs -> Void" "void": "Jobs -> Void"
}, },

View File

@@ -519,6 +519,7 @@
"list-active": "", "list-active": "",
"list-all": "", "list-all": "",
"list-ready": "", "list-ready": "",
"manual-line": "",
"partsqueue": "", "partsqueue": "",
"void": "" "void": ""
}, },

View File

@@ -519,6 +519,7 @@
"list-active": "", "list-active": "",
"list-all": "", "list-all": "",
"list-ready": "", "list-ready": "",
"manual-line": "",
"partsqueue": "", "partsqueue": "",
"void": "" "void": ""
}, },