RO into IO merge as of 02/05/2024.
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import {useSplitClient} from "@splitsoftware/splitio-react";
|
||||
import {Button, Result} from "antd";
|
||||
import { useSplitClient } from "@splitsoftware/splitio-react";
|
||||
import { Button, Result } from "antd";
|
||||
import LogRocket from "logrocket";
|
||||
import React, {lazy, Suspense, useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Route, Routes} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
|
||||
@@ -14,15 +14,19 @@ import LoadingSpinner from "../components/loading-spinner/loading-spinner.compon
|
||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||
import LandingPage from "../pages/landing/landing.page";
|
||||
import TechPageContainer from "../pages/tech/tech.page.container";
|
||||
import {setOnline} from "../redux/application/application.actions";
|
||||
import {selectOnline} from "../redux/application/application.selectors";
|
||||
import {checkUserSession} from "../redux/user/user.actions";
|
||||
import {selectBodyshop, selectCurrentEula, selectCurrentUser,} from "../redux/user/user.selectors";
|
||||
import { setOnline } from "../redux/application/application.actions";
|
||||
import { selectOnline } from "../redux/application/application.selectors";
|
||||
import { checkUserSession } from "../redux/user/user.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentEula,
|
||||
selectCurrentUser,
|
||||
} from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/betaHandler";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
const ResetPassword = lazy(() =>
|
||||
import("../pages/reset-password/reset-password.component")
|
||||
);
|
||||
@@ -37,18 +41,24 @@ const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
currentEula: selectCurrentEula
|
||||
currentEula: selectCurrentEula,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
|
||||
});
|
||||
|
||||
export function App({bodyshop, checkUserSession, currentUser, online, setOnline, currentEula}) {
|
||||
export function App({
|
||||
bodyshop,
|
||||
checkUserSession,
|
||||
currentUser,
|
||||
online,
|
||||
setOnline,
|
||||
currentEula,
|
||||
}) {
|
||||
const client = useSplitClient().client;
|
||||
const [listenersAdded, setListenersAdded] = useState(false)
|
||||
const {t} = useTranslation();
|
||||
|
||||
const [listenersAdded, setListenersAdded] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!navigator.onLine) {
|
||||
@@ -65,14 +75,14 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
|
||||
useEffect(() => {
|
||||
const offlineListener = (e) => {
|
||||
setOnline(false);
|
||||
}
|
||||
};
|
||||
|
||||
const onlineListener = (e) => {
|
||||
setOnline(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (!listenersAdded) {
|
||||
console.log('Added events for offline and online');
|
||||
console.log("Added events for offline and online");
|
||||
window.addEventListener("offline", offlineListener);
|
||||
window.addEventListener("online", onlineListener);
|
||||
setListenersAdded(true);
|
||||
@@ -81,7 +91,7 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
|
||||
return () => {
|
||||
window.removeEventListener("offline", offlineListener);
|
||||
window.removeEventListener("online", onlineListener);
|
||||
}
|
||||
};
|
||||
}, [setOnline, listenersAdded]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -90,16 +100,26 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
|
||||
|
||||
if (
|
||||
client.getTreatment("LogRocket_Tracking") === "on" ||
|
||||
window.location.hostname === 'beta.imex.online'
|
||||
window.location.hostname ===
|
||||
InstanceRenderMgr({
|
||||
imex: "beta.imex.online",
|
||||
rome: "beta.romeonline.io",
|
||||
})
|
||||
) {
|
||||
console.log("LR Start");
|
||||
LogRocket.init("gvfvfw/bodyshopapp");
|
||||
LogRocket.init(
|
||||
InstanceRenderMgr({
|
||||
imex: "gvfvfw/bodyshopapp",
|
||||
rome: "rome-online/rome-online",
|
||||
promanager: "", //TODO:AIO Add in log rocket for promanager instances.
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [bodyshop, client, currentUser.authorized]);
|
||||
|
||||
if (currentUser.authorized === null) {
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")}/>;
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||
}
|
||||
|
||||
handleBeta();
|
||||
@@ -124,29 +144,102 @@ export function App({bodyshop, checkUserSession, currentUser, online, setOnline,
|
||||
);
|
||||
|
||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula/>
|
||||
return <Eula />;
|
||||
}
|
||||
|
||||
// Any route that is not assigned and matched will default to the Landing Page component
|
||||
return (
|
||||
<Suspense fallback={<LoadingSpinner message="ImEX Online"/>}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<LoadingSpinner
|
||||
message={InstanceRenderMgr({
|
||||
imex: "ImEX Online",
|
||||
rome: "Rome Online",
|
||||
promanager: "ProManager",
|
||||
})}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Routes>
|
||||
<Route path="*" element={<ErrorBoundary><LandingPage/></ErrorBoundary>}/>
|
||||
<Route path="/signin" element={<ErrorBoundary><SignInPage/></ErrorBoundary>}/>
|
||||
<Route path="/resetpassword" element={<ErrorBoundary><ResetPassword/></ErrorBoundary>}/>
|
||||
<Route path="/csi/:surveyId" element={<ErrorBoundary><CsiPage/></ErrorBoundary>}/>
|
||||
<Route path="/disclaimer" element={<ErrorBoundary><DisclaimerPage/></ErrorBoundary>}/>
|
||||
<Route path="/mp/:paymentIs" element={<ErrorBoundary><MobilePaymentContainer/></ErrorBoundary>}/>
|
||||
<Route path="/manage/*"
|
||||
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="*" element={<ManagePage/>}/>
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<LandingPage />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/signin"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<SignInPage />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/resetpassword"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<ResetPassword />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/csi/:surveyId"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<CsiPage />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/disclaimer"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<DisclaimerPage />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/mp/:paymentIs"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<MobilePaymentContainer />
|
||||
</ErrorBoundary>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/manage/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
<Route path="*" element={<ManagePage />} />
|
||||
</Route>
|
||||
<Route path="/tech/*"
|
||||
element={<ErrorBoundary><PrivateRoute isAuthorized={currentUser.authorized}/></ErrorBoundary>}>
|
||||
<Route path="*" element={<TechPageContainer/>}/>
|
||||
<Route
|
||||
path="/tech/*"
|
||||
element={
|
||||
<ErrorBoundary>
|
||||
<PrivateRoute
|
||||
isAuthorized={currentUser.authorized}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
}
|
||||
>
|
||||
<Route path="*" element={<TechPageContainer />} />
|
||||
</Route>
|
||||
<Route path="/edit/*" element={<PrivateRoute isAuthorized={currentUser.authorized}/>}>
|
||||
<Route path="*" element={<DocumentEditorContainer/>}/>
|
||||
<Route
|
||||
path="/edit/*"
|
||||
element={
|
||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||
}
|
||||
>
|
||||
<Route path="*" element={<DocumentEditorContainer />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {defaultsDeep} from "lodash";
|
||||
import {theme} from "antd";
|
||||
import InstanceRenderMgr from '../utils/instanceRenderMgr'
|
||||
|
||||
const {defaultAlgorithm, darkAlgorithm} = theme;
|
||||
|
||||
@@ -23,7 +24,10 @@ const defaultTheme = {
|
||||
}
|
||||
},
|
||||
token: {
|
||||
colorPrimary: '#1677ff'
|
||||
colorPrimary: InstanceRenderMgr({
|
||||
imex: "#1677ff",
|
||||
rome:"#326ade"}) ,
|
||||
colorInfo: "#326ade"
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,4 +61,4 @@ const finaltheme = {
|
||||
algorithm: isDarkMode ? darkAlgorithm : defaultAlgorithm,
|
||||
...defaultsDeep(currentTheme, defaultTheme)
|
||||
}
|
||||
export default finaltheme;
|
||||
export default finaltheme;
|
||||
|
||||
BIN
client/src/assets/RomeOnline.png
Normal file
BIN
client/src/assets/RomeOnline.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
BIN
client/src/assets/RomeOnlineBlue.png
Normal file
BIN
client/src/assets/RomeOnlineBlue.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
BIN
client/src/assets/romelogo.png
Normal file
BIN
client/src/assets/romelogo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
@@ -1,4 +1,5 @@
|
||||
import {useApolloClient, useMutation} from "@apollo/client";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Button, Checkbox, Form, Modal, notification, Space} from "antd";
|
||||
import _ from "lodash";
|
||||
import React, {useEffect, useMemo, useState} from "react";
|
||||
@@ -57,9 +58,21 @@ function BillEnterModalContainer({
|
||||
"enter_bill_generate_label",
|
||||
false
|
||||
);
|
||||
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const formValues = useMemo(() => {
|
||||
return {
|
||||
...billEnterModal.context.bill,
|
||||
//Added as a part of IO-2436 for capturing parts price changes.
|
||||
billlines: billEnterModal.context?.bill?.billlines?.map((line) => ({
|
||||
...line,
|
||||
original_actual_price: line.actual_price,
|
||||
})),
|
||||
jobid:
|
||||
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
|
||||
federal_tax_rate:
|
||||
@@ -92,6 +105,7 @@ function BillEnterModalContainer({
|
||||
} = values;
|
||||
|
||||
let adjustmentsToInsert = {};
|
||||
let payrollAdjustmentsToInsert = [];
|
||||
|
||||
const r1 = await insertBill({
|
||||
variables: {
|
||||
@@ -107,14 +121,33 @@ function BillEnterModalContainer({
|
||||
lbr_adjustment,
|
||||
location: lineLocation,
|
||||
part_type,
|
||||
create_ppc,
|
||||
original_actual_price,
|
||||
...restI
|
||||
} = i;
|
||||
|
||||
if (deductedfromlbr) {
|
||||
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||
restI.actual_price / lbr_adjustment.rate;
|
||||
if (Enhanced_Payroll.treatment === "on") {
|
||||
if (
|
||||
deductedfromlbr &&
|
||||
true //payroll is on
|
||||
) {
|
||||
payrollAdjustmentsToInsert.push({
|
||||
id: i.joblineid,
|
||||
convertedtolbr: true,
|
||||
convertedtolbr_data: {
|
||||
mod_lb_hrs: lbr_adjustment.mod_lb_hrs * -1,
|
||||
mod_lbr_ty: lbr_adjustment.mod_lbr_ty,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (deductedfromlbr) {
|
||||
adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] =
|
||||
(adjustmentsToInsert[lbr_adjustment.mod_lbr_ty] || 0) -
|
||||
restI.actual_price / lbr_adjustment.rate;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...restI,
|
||||
deductedfromlbr: deductedfromlbr,
|
||||
@@ -140,6 +173,20 @@ function BillEnterModalContainer({
|
||||
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
payrollAdjustmentsToInsert.map((li) => {
|
||||
return updateJobLines({
|
||||
variables: {
|
||||
lineId: li.id,
|
||||
line: {
|
||||
convertedtolbr: li.convertedtolbr,
|
||||
convertedtolbr_data: li.convertedtolbr_data,
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
const adjKeys = Object.keys(adjustmentsToInsert);
|
||||
if (adjKeys.length > 0) {
|
||||
//Query the adjustments, merge, and update them.
|
||||
@@ -248,6 +295,14 @@ function BillEnterModalContainer({
|
||||
location: li.location || location,
|
||||
status:
|
||||
bodyshop.md_order_statuses.default_received || "Received*",
|
||||
//Added parts price changes.
|
||||
...(li.create_ppc &&
|
||||
li.original_actual_price !== li.actual_price
|
||||
? {
|
||||
act_price_before_ppc: li.original_actual_price,
|
||||
act_price: li.actual_price,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -316,12 +371,12 @@ function BillEnterModalContainer({
|
||||
});
|
||||
|
||||
if (enterAgain) {
|
||||
form.resetFields();
|
||||
form.resetFields();
|
||||
// form.resetFields();
|
||||
form.setFieldsValue({
|
||||
...formValues,
|
||||
billlines: [],
|
||||
});
|
||||
form.resetFields();
|
||||
} else {
|
||||
toggleModalVisible();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import React from "react";
|
||||
import {
|
||||
PlusCircleFilled,
|
||||
MinusCircleFilled,
|
||||
WarningOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {Form, Button, InputNumber, Input, Select, Switch, Space} from "antd";
|
||||
import {MinusCircleFilled, PlusCircleFilled, WarningOutlined,} from "@ant-design/icons";
|
||||
import {Button, Form, Input, InputNumber, Select, Space, Switch} from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
|
||||
@@ -21,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import BillFormLines from "./bill-form.lines.component";
|
||||
import {CalculateBillTotal} from "./bill-form.totals.utility";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -68,17 +69,17 @@ export function BillFormComponent({
|
||||
};
|
||||
|
||||
const handleFederalTaxExemptSwitchToggle = (checked) => {
|
||||
// Early gate
|
||||
if (!checked) return;
|
||||
const values = form.getFieldsValue("billlines");
|
||||
// Gate bill lines
|
||||
if (!values?.billlines?.length) return;
|
||||
// Early gate
|
||||
if (!checked) return;
|
||||
const values = form.getFieldsValue("billlines");
|
||||
// Gate bill lines
|
||||
if (!values?.billlines?.length) return;
|
||||
|
||||
const billlines = values.billlines.map((b) => {
|
||||
b.applicable_taxes.federal = false;
|
||||
return b;
|
||||
});
|
||||
form.setFieldsValue({billlines});
|
||||
const billlines = values.billlines.map((b) => {
|
||||
b.applicable_taxes.federal = false;
|
||||
return b;
|
||||
});
|
||||
form.setFieldsValue({ billlines });
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -368,13 +369,17 @@ export function BillFormComponent({
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled}/>
|
||||
</Form.Item>
|
||||
{
|
||||
InstanceRenderManager({imex:
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled} />
|
||||
</Form.Item> })
|
||||
|
||||
}
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.state_tax_rate")}
|
||||
@@ -382,22 +387,26 @@ export function BillFormComponent({
|
||||
>
|
||||
<CurrencyInput min={0} disabled={disabled}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0}/>
|
||||
</Form.Item>
|
||||
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
||||
<Form.Item
|
||||
span={2}
|
||||
label={t("bills.labels.federal_tax_exempt")}
|
||||
name="federal_tax_exempt"
|
||||
>
|
||||
<Switch onChange={handleFederalTaxExemptSwitchToggle}/>
|
||||
</Form.Item>
|
||||
) : null}
|
||||
{
|
||||
InstanceRenderManager({imex: <>
|
||||
<Form.Item
|
||||
span={3}
|
||||
label={t("bills.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
|
||||
<Form.Item
|
||||
span={2}
|
||||
label={t("bills.labels.federal_tax_exempt")}
|
||||
name="federal_tax_exempt"
|
||||
>
|
||||
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
|
||||
</Form.Item>
|
||||
) : null}
|
||||
</>})
|
||||
}
|
||||
<Form.Item shouldUpdate span={13}>
|
||||
{() => {
|
||||
const values = form.getFieldsValue([
|
||||
@@ -423,21 +432,25 @@ export function BillFormComponent({
|
||||
value={totals.subtotal.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("bills.labels.federal_tax")}
|
||||
value={totals.federalTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
{
|
||||
InstanceRenderManager({imex: <Statistic
|
||||
title={t("bills.labels.federal_tax")}
|
||||
value={totals.federalTax.toFormat()}
|
||||
precision={2}
|
||||
/> })
|
||||
}
|
||||
<Statistic
|
||||
title={t("bills.labels.state_tax")}
|
||||
value={totals.stateTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
<Statistic
|
||||
title={t("bills.labels.local_tax")}
|
||||
value={totals.localTax.toFormat()}
|
||||
precision={2}
|
||||
/>
|
||||
{
|
||||
InstanceRenderManager({imex: <Statistic
|
||||
title={t("bills.labels.local_tax")}
|
||||
value={totals.localTax.toFormat()}
|
||||
precision={2}
|
||||
/>})
|
||||
}
|
||||
<Statistic
|
||||
title={t("bills.labels.entered_total")}
|
||||
value={totals.enteredTotal.toFormat()}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {DeleteFilled, DollarCircleFilled} from "@ant-design/icons";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Button, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip,} from "antd";
|
||||
import {Button, Checkbox, Form, Input, InputNumber, Select, Space, Switch, Table, Tooltip,} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
@@ -10,6 +10,7 @@ import CiecaSelect from "../../utils/Ciecaselect";
|
||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -32,9 +33,9 @@ export function BillEnterModalLinesComponent({
|
||||
const {t} = useTranslation();
|
||||
const {setFieldsValue, getFieldsValue, getFieldValue} = form;
|
||||
|
||||
const {treatments: {Simple_Inventory}} = useSplitTreatments({
|
||||
const {treatments: {Simple_Inventory, Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Simple_Inventory"],
|
||||
names: ["Simple_Inventory", "Enhanced_Payroll"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
@@ -87,6 +88,7 @@ export function BillEnterModalLinesComponent({
|
||||
line_desc: opt.line_desc,
|
||||
quantity: opt.part_qty || 1,
|
||||
actual_price: opt.cost,
|
||||
original_actual_price: opt.cost,
|
||||
cost_center: opt.part_type
|
||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||
? opt.part_type !== "PAE"
|
||||
@@ -213,6 +215,46 @@ export function BillEnterModalLinesComponent({
|
||||
}}
|
||||
/>
|
||||
),
|
||||
additional: (record, index) => (
|
||||
InstanceRenderManager({rome: <Form.Item
|
||||
dependencies={["billlines", record.name, "actual_price"]}
|
||||
noStyle
|
||||
>
|
||||
{() => {
|
||||
const billLine = getFieldValue(["billlines", record.name]);
|
||||
const jobLine = lineData.find(
|
||||
(line) => line.id === billLine?.joblineid
|
||||
);
|
||||
|
||||
if (
|
||||
!billEdit &&
|
||||
billLine &&
|
||||
jobLine &&
|
||||
billLine?.actual_price !== jobLine?.act_price
|
||||
) {
|
||||
return (
|
||||
<Space size="small">
|
||||
<Form.Item
|
||||
noStyle
|
||||
label={t("joblines.fields.create_ppc")}
|
||||
key={`${index}ppc`}
|
||||
valuePropName="checked"
|
||||
name={[record.name, "create_ppc"]}
|
||||
>
|
||||
<Checkbox/>
|
||||
</Form.Item>
|
||||
{t("joblines.fields.create_ppc")}
|
||||
</Space>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}}
|
||||
</Form.Item>
|
||||
//Do not need to set for promanager as it will default to Rome.
|
||||
})
|
||||
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.actual_cost"),
|
||||
@@ -354,7 +396,7 @@ export function BillEnterModalLinesComponent({
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled}/>,
|
||||
additional: (record, index) => (
|
||||
<Form.Item shouldUpdate style={{display: "inline-block"}}>
|
||||
<Form.Item shouldUpdate noStyle style={{display: "inline-block"}}>
|
||||
{() => {
|
||||
const price = getFieldValue([
|
||||
"billlines",
|
||||
@@ -369,12 +411,34 @@ export function BillEnterModalLinesComponent({
|
||||
"rate",
|
||||
]);
|
||||
|
||||
const billline = getFieldValue(["billlines", record.name]);
|
||||
|
||||
const jobline = lineData.find(
|
||||
(line) => line.id === billline?.joblineid
|
||||
);
|
||||
|
||||
const employeeTeamName = bodyshop.employee_teams.find(
|
||||
(team) => team.id === jobline?.assigned_team
|
||||
);
|
||||
|
||||
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
|
||||
return (
|
||||
<div>
|
||||
{Enhanced_Payroll.treatment === "on" ?
|
||||
<Space>
|
||||
{t("joblines.fields.assigned_team", {
|
||||
name: employeeTeamName?.name,
|
||||
})}
|
||||
{`${jobline.mod_lb_hrs} units/${t(
|
||||
`joblines.fields.lbr_types.${jobline.mod_lbr_ty}`
|
||||
)}`}
|
||||
</Space> :
|
||||
null}
|
||||
|
||||
<Form.Item
|
||||
label={t("joblines.fields.mod_lbr_ty")}
|
||||
key={`${index}modlbrty`}
|
||||
initialValue={jobline ? jobline.mod_lbr_ty : null}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -428,22 +492,44 @@ export function BillEnterModalLinesComponent({
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.labels.adjustmentrate")}
|
||||
name={[record.name, "lbr_adjustment", "rate"]}
|
||||
initialValue={bodyshop.default_adjustment_rate}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={2} min={0.01}/>
|
||||
</Form.Item>
|
||||
{price &&
|
||||
adjustmentRate &&
|
||||
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||
{Enhanced_Payroll.treatment === "on" ? (
|
||||
<Form.Item
|
||||
label={t("billlines.labels.mod_lbr_adjustment")}
|
||||
name={[record.name, "lbr_adjustment", "mod_lb_hrs"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber
|
||||
precision={5}
|
||||
min={0.01}
|
||||
max={jobline ? jobline.mod_lb_hrs : 0}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item
|
||||
label={t("jobs.labels.adjustmentrate")}
|
||||
name={[record.name, "lbr_adjustment", "rate"]}
|
||||
initialValue={bodyshop.default_adjustment_rate}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={2} min={0.01}/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Space>
|
||||
{price &&
|
||||
adjustmentRate &&
|
||||
`${(price / adjustmentRate).toFixed(1)} hrs`}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
return <></>;
|
||||
@@ -451,22 +537,24 @@ export function BillEnterModalLinesComponent({
|
||||
</Form.Item>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("billlines.fields.federal_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.federal",
|
||||
editable: true,
|
||||
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}fedtax`,
|
||||
valuePropName: "checked",
|
||||
initialValue:
|
||||
form.getFieldValue("federal_tax_exempt") === true ? false : true,
|
||||
name: [field.name, "applicable_taxes", "federal"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled}/>,
|
||||
},
|
||||
|
||||
...InstanceRenderManager({imex: { title: t("billlines.fields.federal_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.federal",
|
||||
editable: true,
|
||||
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}fedtax`,
|
||||
valuePropName: "checked",
|
||||
// initialValue: true,
|
||||
name: [field.name, "applicable_taxes", "federal"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,}})
|
||||
|
||||
|
||||
,
|
||||
{
|
||||
title: t("billlines.fields.state_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.state",
|
||||
@@ -481,20 +569,22 @@ export function BillEnterModalLinesComponent({
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled}/>,
|
||||
},
|
||||
{
|
||||
|
||||
...InstanceRenderManager({imex: {
|
||||
title: t("billlines.fields.local_tax_applicable"),
|
||||
dataIndex: "applicable_taxes.local",
|
||||
editable: true,
|
||||
|
||||
formItemProps: (field) => {
|
||||
return {
|
||||
key: `${field.index}localtax`,
|
||||
valuePropName: "checked",
|
||||
name: [field.name, "applicable_taxes", "local"],
|
||||
};
|
||||
return {
|
||||
key: `${field.index}localtax`,
|
||||
valuePropName: "checked",
|
||||
name: [field.name, "applicable_taxes", "local"],
|
||||
};
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled}/>,
|
||||
},
|
||||
formInput: (record, index) => <Switch disabled={disabled} />,
|
||||
}})
|
||||
,
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
|
||||
@@ -619,7 +709,7 @@ const EditableCell = ({
|
||||
if (additional)
|
||||
return (
|
||||
<td {...restProps}>
|
||||
<Space size="small">
|
||||
<div size="small">
|
||||
<Form.Item
|
||||
name={dataIndex}
|
||||
labelCol={{span: 0}}
|
||||
@@ -628,7 +718,7 @@ const EditableCell = ({
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
{additional && additional(record, record.name)}
|
||||
</Space>
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
if (wrapper)
|
||||
|
||||
@@ -19,14 +19,14 @@ export const CalculateBillTotal = (invoice) => {
|
||||
}).multiply(i.quantity || 1);
|
||||
|
||||
subtotal = subtotal.add(itemTotal);
|
||||
if (i.applicable_taxes.federal) {
|
||||
if (i.applicable_taxes?.federal) {
|
||||
federalTax = federalTax.add(
|
||||
itemTotal.percentage(federal_tax_rate || 0)
|
||||
);
|
||||
}
|
||||
if (i.applicable_taxes.state)
|
||||
if (i.applicable_taxes?.state)
|
||||
stateTax = stateTax.add(itemTotal.percentage(state_tax_rate || 0));
|
||||
if (i.applicable_taxes.local)
|
||||
if (i.applicable_taxes?.local)
|
||||
localTax = localTax.add(itemTotal.percentage(local_tax_rate || 0));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {Select} from "antd";
|
||||
import React, {forwardRef} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import InstanceRenderMgr from '../../utils/instanceRenderMgr';
|
||||
|
||||
//To be used as a form element only.
|
||||
const {Option} = Select;
|
||||
@@ -67,6 +68,22 @@ const BillLineSearchSelect = (
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
</span>
|
||||
{
|
||||
InstanceRenderMgr
|
||||
(
|
||||
{
|
||||
rome: item.act_price === 0 && item.mod_lb_hrs > 0 && (
|
||||
<span style={{float: "right", paddingleft: "1rem"}}>
|
||||
{`${item.mod_lb_hrs} units`}
|
||||
</span>
|
||||
)
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
<span style={{float: "right", paddingleft: "1rem"}}>
|
||||
{item.act_price
|
||||
? `$${item.act_price && item.act_price.toFixed(2)}`
|
||||
|
||||
@@ -4,11 +4,7 @@ import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop,
|
||||
|
||||
@@ -187,7 +187,7 @@ export const uploadToCloudinary = async (
|
||||
message: JSON.stringify(documentInsert.errors),
|
||||
}),
|
||||
});
|
||||
return;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,14 +6,8 @@ import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||
import {toggleEmailOverlayVisible} from "../../redux/email/email.actions";
|
||||
import {
|
||||
selectEmailConfig,
|
||||
selectEmailVisible,
|
||||
} from "../../redux/email/email.selectors.js";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import {selectEmailConfig, selectEmailVisible,} from "../../redux/email/email.selectors.js";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import RenderTemplate from "../../utils/RenderTemplate";
|
||||
import {EmailSettings} from "../../utils/TemplateConstants";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {Select, Space, Tag} from "antd";
|
||||
import React, {forwardRef} from "react";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
const {Option} = Select;
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeSearchSelect = ({options, ...props}, ref) => {
|
||||
const EmployeeSearchSelect = ({options, ...props}) => {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -40,4 +40,4 @@ const EmployeeSearchSelect = ({options, ...props}, ref) => {
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export default forwardRef(EmployeeSearchSelect);
|
||||
export default EmployeeSearchSelect;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Select} from "antd";
|
||||
import React, {forwardRef} from "react";
|
||||
import {QUERY_TEAMS} from "../../graphql/employee_teams.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeTeamSearchSelect = ({...props}, ref) => {
|
||||
const {loading, error, data} = useQuery(QUERY_TEAMS);
|
||||
|
||||
if (error) return <AlertComponent message={JSON.stringify(error)}/>;
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
allowClear
|
||||
loading={loading}
|
||||
style={{
|
||||
width: 400,
|
||||
}}
|
||||
options={
|
||||
data
|
||||
? data.employee_teams.map((e) => ({
|
||||
value: JSON.stringify(e),
|
||||
label: e.name,
|
||||
}))
|
||||
: []
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default forwardRef(EmployeeTeamSearchSelect);
|
||||
@@ -6,6 +6,7 @@ import * as Sentry from "@sentry/react";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -37,22 +38,24 @@ class ErrorBoundary extends React.Component {
|
||||
}
|
||||
|
||||
handleErrorSubmit = () => {
|
||||
window.$crisp.push([
|
||||
"do",
|
||||
"message:send",
|
||||
[
|
||||
"text",
|
||||
`I hit the following error: \n\n
|
||||
${this.state.error.message}\n\n
|
||||
${this.state.error.stack}\n\n
|
||||
URL:${window.location} as ${this.props.currentUser.email} for ${
|
||||
InstanceRenderManager({executeFunction:true, imex: () => {
|
||||
window.$crisp.push([
|
||||
"do",
|
||||
"message:send",
|
||||
[
|
||||
"text",
|
||||
`I hit the following error: \n\n
|
||||
${this.state.error.message}\n\n
|
||||
${this.state.error.stack}\n\n
|
||||
URL:${window.location} as ${this.props.currentUser.email} for ${
|
||||
this.props.bodyshop && this.props.bodyshop.name
|
||||
}
|
||||
`,
|
||||
],
|
||||
]);
|
||||
}
|
||||
`,
|
||||
],
|
||||
]);
|
||||
|
||||
window.$crisp.push(["do", "chat:open"]);
|
||||
window.$crisp.push(["do", "chat:open"]);
|
||||
} })
|
||||
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
|
||||
|
||||
// ----
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import Dinero from "dinero.js";
|
||||
import React, {forwardRef} from "react";
|
||||
|
||||
const ReadOnlyFormItem = ({value, type = "text", onChange}, ref) => {
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
const ReadOnlyFormItem = (
|
||||
{bodyshop, value, type = "text", onChange},
|
||||
ref
|
||||
) => {
|
||||
if (!value) return null;
|
||||
switch (type) {
|
||||
case "employee":
|
||||
const emp = bodyshop.employees.find((e) => e.id === value);
|
||||
return `${emp?.first_name} ${emp?.last_name}`;
|
||||
|
||||
case "text":
|
||||
return <div>{value}</div>;
|
||||
case "currency":
|
||||
@@ -14,4 +32,8 @@ const ReadOnlyFormItem = ({value, type = "text", onChange}, ref) => {
|
||||
return <div>{value}</div>;
|
||||
}
|
||||
};
|
||||
export default forwardRef(ReadOnlyFormItem);
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(forwardRef(ReadOnlyFormItem));
|
||||
|
||||
@@ -5,9 +5,7 @@ import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {Link, useNavigate} from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
|
||||
@@ -7,9 +7,7 @@ import {Link, useNavigate} from "react-router-dom";
|
||||
import {GLOBAL_SEARCH_QUERY} from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import OwnerNameDisplay, {
|
||||
OwnerNameDisplayFunction,
|
||||
} from "../owner-name-display/owner-name-display.component";
|
||||
import OwnerNameDisplay, {OwnerNameDisplayFunction,} from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearch() {
|
||||
|
||||
@@ -43,6 +43,7 @@ import {signOutStart} from "../../redux/user/user.actions";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import {FiLogOut} from "react-icons/fi";
|
||||
import {checkBeta, handleBeta, setBeta} from "../../utils/betaHandler";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -174,8 +175,20 @@ function Header({
|
||||
key: 'timetickets',
|
||||
icon: <FieldTimeOutlined/>,
|
||||
label: (<Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>)
|
||||
},
|
||||
{
|
||||
});
|
||||
|
||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||
accountingChildren.push({
|
||||
key: 'ttapprovals',
|
||||
icon: <FieldTimeOutlined/>,
|
||||
label:
|
||||
<Link to="/manage/ttapprovals">
|
||||
{t("menus.header.ttapprovals")}
|
||||
</Link>
|
||||
});
|
||||
}
|
||||
|
||||
accountingChildren.push({
|
||||
key: 'entertimetickets',
|
||||
icon: <Icon component={GiPlayerTime}/>,
|
||||
label: t("menus.header.entertimeticket"),
|
||||
@@ -421,17 +434,19 @@ function Header({
|
||||
icon: <Icon component={QuestionCircleFilled}/>,
|
||||
label: t("menus.header.help"),
|
||||
onClick: () => {
|
||||
window.open("https://help.imex.online/", "_blank");
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'rescue',
|
||||
icon: <Icon component={CarFilled}/>,
|
||||
label: t("menus.header.rescueme"),
|
||||
onClick: () => {
|
||||
window.open("https://imexrescue.com/", "_blank");
|
||||
window.open(InstanceRenderManager({imex:"https://help.imex.online/",rome: "https://rometech.com//", promanager:"https://web-est.com" })
|
||||
|
||||
, "_blank");
|
||||
}
|
||||
},
|
||||
// {
|
||||
// key: 'rescue',
|
||||
// icon: <Icon component={CarFilled}/>,
|
||||
// label: t("menus.header.rescueme"),
|
||||
// onClick: () => {
|
||||
// window.open("https://imexrescue.com/", "_blank");
|
||||
// }
|
||||
// },
|
||||
{
|
||||
key: 'shiftclock',
|
||||
icon: <Icon component={GiPlayerTime}/>,
|
||||
@@ -487,9 +502,9 @@ function Header({
|
||||
key: 'beta-switch',
|
||||
style: {marginLeft: 'auto'},
|
||||
label: (
|
||||
<Tooltip title="A more modern ImEX Online is ready for you to try! You can switch back at any time.">
|
||||
<Tooltip title={`A more modern ${InstanceRenderManager({imex: 'ImEX Online', rome: "Rome Online"})} is ready for you to try! You can switch back at any time.`}>
|
||||
<InfoCircleOutlined/>
|
||||
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
|
||||
<span style={{marginRight: 8}}>Try the new app</span>
|
||||
<Switch
|
||||
checked={betaSwitch}
|
||||
onChange={betaSwitchChange}
|
||||
|
||||
@@ -1,27 +1,14 @@
|
||||
import {AlertFilled} from "@ant-design/icons";
|
||||
import {
|
||||
Button,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Form,
|
||||
Input,
|
||||
notification,
|
||||
Popover,
|
||||
Select,
|
||||
Space,
|
||||
} from "antd";
|
||||
import {Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space,} from "antd";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useNavigate, useLocation} from "react-router-dom";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
} from "../../redux/messaging/messaging.actions";
|
||||
import {openChatByPhone, setMessage,} from "../../redux/messaging/messaging.actions";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
|
||||
@@ -39,7 +39,8 @@ export default function JobBillsTotalComponent({
|
||||
);
|
||||
|
||||
if (pol.cm_received === null) {
|
||||
return; // Skip this calculation for bills posted prior to the CNR change.
|
||||
return; //TODO:AIO This was previously removed. Check if functionality impacted.
|
||||
// Skip this calculation for bills posted prior to the CNR change.
|
||||
} else {
|
||||
if (pol.cm_received === false) {
|
||||
totalReturnsMarkedNotReceived = totalReturnsMarkedNotReceived.add(
|
||||
@@ -87,7 +88,8 @@ export default function JobBillsTotalComponent({
|
||||
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
||||
.add(Dinero(totals.parts.sublets.total))
|
||||
.add(Dinero(totals.additional.shipping))
|
||||
.add(Dinero(totals.additional.towing));
|
||||
.add(Dinero(totals.additional.towing))
|
||||
.add(Dinero(totals.additional.additionalCosts)); //TODO:AIO Additional costs were captured for Rome, but not imex. This may need to be evaluated?
|
||||
|
||||
const discrepancy = totalPartsSublet.subtract(billTotals);
|
||||
|
||||
|
||||
@@ -1,48 +1,53 @@
|
||||
import {Button, notification} from "antd";
|
||||
import Axios from "axios";
|
||||
import React, {useState} from "react";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {UPDATE_JOB} from "../../graphql/jobs.queries";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export default function JobCalculateTotals({job, disabled}) {
|
||||
export default function JobCalculateTotals({job, disabled, refetch}) {
|
||||
const {t} = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
|
||||
const handleCalculate = async () => {
|
||||
setLoading(true);
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: job,
|
||||
})
|
||||
).data;
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
const result = await updateJob({
|
||||
refetchQueries: ["GET_JOB_BY_PK"],
|
||||
awaitRefetchQueries: true,
|
||||
variables: {
|
||||
jobId: job.id,
|
||||
job: {
|
||||
job_totals: newTotals,
|
||||
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
|
||||
"0.00"
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({message: t("jobs.successes.updated")});
|
||||
} else {
|
||||
await Axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
});
|
||||
|
||||
if (refetch) refetch();
|
||||
// const result = await updateJob({
|
||||
// refetchQueries: ["GET_JOB_BY_PK"],
|
||||
// awaitRefetchQueries: true,
|
||||
// variables: {
|
||||
// jobId: job.id,
|
||||
// job: {
|
||||
// job_totals: newTotals,
|
||||
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat(
|
||||
// "0.00"
|
||||
// ),
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// if (!!!result.errors) {
|
||||
// notification["success"]({ message: t("jobs.successes.updated") });
|
||||
// } else {
|
||||
// notification["error"]({
|
||||
// message: t("jobs.errors.updating", {
|
||||
// error: JSON.stringify(result.errors),
|
||||
// }),
|
||||
// });
|
||||
// }
|
||||
} catch (error) {
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.updating", {
|
||||
error: JSON.stringify(result.errors),
|
||||
error: JSON.stringify(error),
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,17 +8,11 @@ import {connect} from "react-redux";
|
||||
import {useLocation, useNavigate, useParams} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {logImEXEvent} from "../../../../firebase/firebase.utils";
|
||||
import {
|
||||
MARK_APPOINTMENT_ARRIVED,
|
||||
MARK_LATEST_APPOINTMENT_ARRIVED,
|
||||
} from "../../../../graphql/appointments.queries";
|
||||
import {MARK_APPOINTMENT_ARRIVED, MARK_LATEST_APPOINTMENT_ARRIVED,} from "../../../../graphql/appointments.queries";
|
||||
import {UPDATE_JOB} from "../../../../graphql/jobs.queries";
|
||||
import {UPDATE_OWNER} from "../../../../graphql/owners.queries";
|
||||
import {insertAuditTrail} from "../../../../redux/application/application.actions";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../../../redux/user/user.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
||||
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
||||
|
||||
@@ -4,12 +4,8 @@ import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useParams} from "react-router-dom";
|
||||
import {logImEXEvent} from "../../../../firebase/firebase.utils";
|
||||
import {
|
||||
GenerateDocument,
|
||||
GenerateDocuments,
|
||||
} from "../../../../utils/RenderTemplate";
|
||||
import {GenerateDocument, GenerateDocuments,} from "../../../../utils/RenderTemplate";
|
||||
import {TemplateList} from "../../../../utils/TemplateConstants";
|
||||
|
||||
const TemplateListGenerated = TemplateList();
|
||||
|
||||
export default function JobIntakeTemplateList({templates}) {
|
||||
|
||||
@@ -7,9 +7,9 @@ import {connect} from "react-redux";
|
||||
import {useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {UPDATE_JOB_LINES_IOU} from "../../graphql/jobs-lines.queries";
|
||||
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import {CreateIouForJob} from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util";
|
||||
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -22,13 +22,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU);
|
||||
|
||||
export function JobCreateIOU({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
job,
|
||||
selectedJobLines,
|
||||
technician,
|
||||
}) {
|
||||
export function JobCreateIOU({bodyshop, currentUser, job, selectedJobLines, technician}) {
|
||||
const {t} = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const client = useApolloClient();
|
||||
@@ -88,19 +82,13 @@ export function JobCreateIOU({
|
||||
title={t("jobs.labels.createiouwarning")}
|
||||
onConfirm={handleCreateIou}
|
||||
disabled={
|
||||
!selectedJobLines ||
|
||||
selectedJobLines.length === 0 ||
|
||||
!job.converted ||
|
||||
technician
|
||||
!selectedJobLines || selectedJobLines.length === 0 || !job.converted || technician
|
||||
}
|
||||
>
|
||||
<Button
|
||||
loading={loading}
|
||||
disabled={
|
||||
!selectedJobLines ||
|
||||
selectedJobLines.length === 0 ||
|
||||
!job.converted ||
|
||||
technician
|
||||
!selectedJobLines || selectedJobLines.length === 0 || !job.converted || technician
|
||||
}
|
||||
>
|
||||
{t("jobs.actions.createiou")}
|
||||
|
||||
@@ -32,9 +32,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
const span = {
|
||||
sm: {span: 24},
|
||||
md: {span: 12},
|
||||
lg: {span: 8},
|
||||
lg: {span: 24},
|
||||
xl: {span: 12},
|
||||
xxl: {span: 8},
|
||||
};
|
||||
|
||||
export function JobDetailCards({bodyshop, setPrintCenterContext}) {
|
||||
@@ -137,12 +137,6 @@ export function JobDetailCards({bodyshop, setPrintCenterContext}) {
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
@@ -163,6 +157,12 @@ export function JobDetailCards({bodyshop, setPrintCenterContext}) {
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
) : null}
|
||||
|
||||
@@ -1,16 +1,120 @@
|
||||
import {Table} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
|
||||
import PartsStatusPie from "../parts-status-pie/parts-status-pie.component";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsPartsComponent({loading, data}) {
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {onlyUnique} from "../../utils/arrayHelper";
|
||||
import {alphaSort} from "../../utils/sorters";
|
||||
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
|
||||
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobDetailCardsPartsComponent);
|
||||
|
||||
export function JobDetailCardsPartsComponent({loading, data, jobRO}) {
|
||||
const {t} = useTranslation();
|
||||
const {joblines_status} = data;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
fixed: "left",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
onCell: (record) => ({
|
||||
className: record.manual_line && "job-line-manual",
|
||||
style: {
|
||||
...(record.critical ? {boxShadow: " -.5em 0 0 #FFC107"} : {}),
|
||||
},
|
||||
}),
|
||||
width: "30%",
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_type"),
|
||||
dataIndex: "part_type",
|
||||
key: "part_type",
|
||||
width: "15%",
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
t(`joblines.fields.part_types.${a.part_type}`),
|
||||
t(`joblines.fields.part_types.${b.part_type}`)
|
||||
),
|
||||
render: (text, record) =>
|
||||
record.part_type
|
||||
? t(`joblines.fields.part_types.${record.part_type}`)
|
||||
: null,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty",
|
||||
width: "10%",
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.notes"),
|
||||
dataIndex: "notes",
|
||||
key: "notes",
|
||||
render: (text, record) => (
|
||||
<JobLineNotePopup disabled={jobRO} jobline={record}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.location"),
|
||||
dataIndex: "location",
|
||||
key: "location",
|
||||
sorter: (a, b) => alphaSort(a.location, b.location),
|
||||
render: (text, record) => (
|
||||
<JobLineLocationPopup jobline={record} disabled={jobRO}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
filters:
|
||||
(data &&
|
||||
data.joblines
|
||||
?.map((l) => l.status)
|
||||
.filter(onlyUnique)
|
||||
.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s],
|
||||
};
|
||||
})) ||
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => (
|
||||
<JobLineStatusPopup jobline={record} disabled={jobRO}/>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||
<PartsStatusPie joblines_status={joblines_status}/>
|
||||
<Table
|
||||
key="id"
|
||||
columns={columns}
|
||||
dataSource={data ? data.joblines : []}
|
||||
/>
|
||||
</CardTemplate>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,19 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {DateFormatter} from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
export default function JobLinesExpander({jobline, jobid}) {
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
|
||||
|
||||
export function JobLinesExpander({jobline, jobid, bodyshop}) {
|
||||
const {t} = useTranslation();
|
||||
const {loading, error, data} = useQuery(GET_JOB_LINE_ORDERS, {
|
||||
fetchPolicy: "network-only",
|
||||
@@ -23,7 +35,7 @@ export default function JobLinesExpander({jobline, jobid}) {
|
||||
|
||||
return (
|
||||
<Row>
|
||||
<Col md={24} lg={12}>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>
|
||||
{t("parts_orders.labels.parts_orders")}
|
||||
</Typography.Title>
|
||||
@@ -52,7 +64,7 @@ export default function JobLinesExpander({jobline, jobid}) {
|
||||
]
|
||||
}
|
||||
/> </Col>
|
||||
<Col md={24} lg={12}>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<Timeline
|
||||
items={
|
||||
@@ -90,6 +102,37 @@ export default function JobLinesExpander({jobline, jobid}) {
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>
|
||||
{t("parts_dispatch.labels.parts_dispatch")}
|
||||
</Typography.Title>
|
||||
<Timeline items={
|
||||
data.parts_dispatch_lines.length > 0 ? (
|
||||
data.parts_dispatch_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical"/>} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>
|
||||
{line.parts_dispatch.number}
|
||||
</Link>
|
||||
{
|
||||
bodyshop.employees.find(
|
||||
(e) => e.id === line.parts_dispatch.employeeid
|
||||
)?.first_name
|
||||
}
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
</Space>
|
||||
</Space>
|
||||
)
|
||||
}))
|
||||
) : ({
|
||||
key: 'dispatch-lines',
|
||||
children: t("parts_orders.labels.notyetordered"),
|
||||
})
|
||||
}/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Form, notification, Popover, Tooltip} from "antd";
|
||||
import {t} from "i18next";
|
||||
import React, {useState} from "react";
|
||||
import {UPDATE_LINE_PPC} from "../../graphql/jobs-lines.queries";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
|
||||
import axios from "axios";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function JobLinesPartPriceChange({job, line, refetch}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [updatePartPrice] = useMutation(UPDATE_LINE_PPC);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await updatePartPrice({
|
||||
variables: {
|
||||
id: line.id,
|
||||
jobline: {
|
||||
act_price_before_ppc: line.act_price_before_ppc
|
||||
? line.act_price_before_ppc
|
||||
: line.act_price,
|
||||
act_price: values.act_price,
|
||||
},
|
||||
},
|
||||
});
|
||||
await axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("joblines.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
if (refetch) refetch();
|
||||
} else {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("joblines.successes.saved"),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("joblines.errors.saving", {error: JSON.stringify(error)}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const popcontent = (
|
||||
<Form layout="vertical" onFinish={handleFinish} initialValues={{act_price: line.act_price}}>
|
||||
<Form.Item
|
||||
name="act_price"
|
||||
label={t("jobs.labels.act_price_ppc")}
|
||||
rules={[{required: true}]}
|
||||
>
|
||||
<CurrencyFormItemComponent/>
|
||||
</Form.Item>
|
||||
<Button loading={loading} htmlType="primary">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
|
||||
return (
|
||||
<JobLineConvertToLabor jobline={line} job={job}>
|
||||
<Popover trigger="click" disabled={line.manual_line || InstanceRenderManager({imex:false, rome:true})} content={popcontent}>
|
||||
<CurrencyFormatter>
|
||||
{line.db_ref === "900510" || line.db_ref === "900511"
|
||||
? line.prt_dsmk_m
|
||||
: line.act_price}
|
||||
</CurrencyFormatter>
|
||||
{line.prt_dsmk_p && line.prt_dsmk_p !== 0 ? (
|
||||
<span style={{marginLeft: ".2rem"}}>{`(${line.prt_dsmk_p}%)`}</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{line.act_price_before_ppc && line.act_price_before_ppc !== 0 ? (
|
||||
<Tooltip title={t("jobs.labels.ppc")}>
|
||||
<span style={{marginLeft: ".2rem", color: "tomato"}}>
|
||||
(
|
||||
<CurrencyFormatter>{line.act_price_before_ppc}</CurrencyFormatter>
|
||||
)
|
||||
</span>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</Popover>
|
||||
</JobLineConvertToLabor>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
import {onlyUnique} from "../../utils/arrayHelper";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {alphaSort} from "../../utils/sorters";
|
||||
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
|
||||
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
|
||||
@@ -30,13 +29,18 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
|
||||
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container";
|
||||
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
||||
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import _ from "lodash";
|
||||
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
|
||||
import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-send-parts-price-change.component";
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import JobLinesExpander from "./job-lines-expander.component";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
|
||||
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
|
||||
import JoblineTeamAssignment from "../job-line-team-assignment/job-line-team-assignmnent.component";
|
||||
import JobLineDispatchButton from "../job-line-dispatch-button/job-line-dispatch-button.component";
|
||||
import JobLineBulkAssignComponent from "../job-line-bulk-assign/job-line-bulk-assign.component";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -68,6 +72,11 @@ export function JobLinesComponent({
|
||||
setBillEnterContext,
|
||||
}) {
|
||||
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const [selectedLines, setSelectedLines] = useState([]);
|
||||
const [state, setState] = useState({
|
||||
@@ -113,10 +122,21 @@ export function JobLinesComponent({
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
render: (text, record) =>
|
||||
`${record.oem_partno || ""} ${
|
||||
record.alt_partno ? `(${record.alt_partno})` : ""
|
||||
}`.trim(),
|
||||
onCell: (record) => ({
|
||||
className: record.manual_line && "job-line-manual",
|
||||
style: {
|
||||
...(record.parts_dispatch_lines[0]?.accepted_at
|
||||
? {boxShadow: " -.5em 0 0 #FFC107"}
|
||||
: {}),
|
||||
},
|
||||
}),
|
||||
render: (text, record) => (
|
||||
<span class="ant-table-cell-content">
|
||||
{`${record.oem_partno || ""} ${
|
||||
record.alt_partno ? `(${record.alt_partno})` : ""
|
||||
}`.trim()}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.op_code_desc"),
|
||||
@@ -212,20 +232,7 @@ export function JobLinesComponent({
|
||||
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<JobLineConvertToLabor jobline={record} job={job}>
|
||||
<CurrencyFormatter>
|
||||
{record.db_ref === "900510" || record.db_ref === "900511"
|
||||
? record.prt_dsmk_m
|
||||
: record.act_price}
|
||||
</CurrencyFormatter>
|
||||
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
||||
<span
|
||||
style={{marginLeft: ".2rem"}}
|
||||
>{`(${record.prt_dsmk_p}%)`}</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</JobLineConvertToLabor>
|
||||
<JobLinesPartPriceChange line={record} job={job} refetch={refetch}/>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -278,6 +285,23 @@ export function JobLinesComponent({
|
||||
state.sortedInfo.columnKey === "line_ind" && state.sortedInfo.order,
|
||||
responsive: ["md"],
|
||||
},
|
||||
...(Enhanced_Payroll.treatment === "on"
|
||||
? [
|
||||
{
|
||||
title: t("joblines.fields.assigned_team"),
|
||||
dataIndex: "assigned_team",
|
||||
key: "assigned_team",
|
||||
render: (text, record) => (
|
||||
<JoblineTeamAssignment
|
||||
disabled={jobRO}
|
||||
jobline={record}
|
||||
jobId={job.id}
|
||||
/>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
title: t("joblines.fields.notes"),
|
||||
dataIndex: "notes",
|
||||
@@ -396,7 +420,11 @@ export function JobLinesComponent({
|
||||
setSelectedLines((selectedLines) =>
|
||||
_.uniq([
|
||||
...selectedLines,
|
||||
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
|
||||
...jobLines.filter(
|
||||
(item) =>
|
||||
markedTypes.includes(item.part_type) ||
|
||||
markedTypes.includes(item.mod_lbr_ty)
|
||||
),
|
||||
])
|
||||
);
|
||||
}
|
||||
@@ -410,6 +438,21 @@ export function JobLinesComponent({
|
||||
{key: "PAL", label: t("joblines.fields.part_types.PAL")},
|
||||
{key: "PAS", label: t("joblines.fields.part_types.PAS")},
|
||||
{type: 'divider'},
|
||||
{key: "LAA", label: t("joblines.fields.lbr_types.LAA")},
|
||||
{key: "LAB", label: t("joblines.fields.lbr_types.LAB")},
|
||||
{key: "LAD", label: t("joblines.fields.part_types.LAD")},
|
||||
{key: "LAE", label: t("joblines.fields.part_types.LAE")},
|
||||
{key: "LAF", label: t("joblines.fields.part_types.LAF")},
|
||||
{key: "LAG", label: t("joblines.fields.part_types.LAG")},
|
||||
{key: "LAM", label: t("joblines.fields.part_types.LAM")},
|
||||
{key: "LAR", label: t("joblines.fields.part_types.LAR")},
|
||||
{key: "LAS", label: t("joblines.fields.part_types.LAS")},
|
||||
{key: "LAU", label: t("joblines.fields.part_types.LAU")},
|
||||
{key: "LA1", label: t("joblines.fields.part_types.LA1")},
|
||||
{key: "LA2", label: t("joblines.fields.part_types.LA2")},
|
||||
{key: "LA3", label: t("joblines.fields.part_types.LA3")},
|
||||
{key: "LA4", label: t("joblines.fields.part_types.LA4")},
|
||||
{type: 'divider'},
|
||||
{key: "clear", label: t("general.labels.clear")},
|
||||
]
|
||||
};
|
||||
@@ -433,6 +476,18 @@ export function JobLinesComponent({
|
||||
</Space>
|
||||
</Tag>
|
||||
)}
|
||||
<JobLineDispatchButton
|
||||
selectedLines={selectedLines}
|
||||
setSelectedLines={setSelectedLines}
|
||||
job={job}
|
||||
/>
|
||||
{Enhanced_Payroll.treatment === "on" && (
|
||||
<JobLineBulkAssignComponent
|
||||
selectedLines={selectedLines}
|
||||
setSelectedLines={setSelectedLines}
|
||||
job={job}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
disabled={
|
||||
(job && !job.converted) ||
|
||||
@@ -441,15 +496,6 @@ export function JobLinesComponent({
|
||||
technician
|
||||
}
|
||||
onClick={() => {
|
||||
// setPartsOrderContext({
|
||||
// actions: { refetch: refetch },
|
||||
// context: {
|
||||
// jobId: job.id,
|
||||
// job: job,
|
||||
// linesToOrder: selectedLines,
|
||||
// },
|
||||
// });
|
||||
|
||||
setBillEnterContext({
|
||||
actions: {refetch: refetch},
|
||||
context: {
|
||||
@@ -556,6 +602,9 @@ export function JobLinesComponent({
|
||||
>
|
||||
{t("joblines.actions.new")}
|
||||
</Button>
|
||||
{bodyshop.region_config.toLowerCase().startsWith("us") && (
|
||||
<JobSendPartPriceChangeComponent job={job}/>
|
||||
)}
|
||||
<JobCreateIOU job={job} selectedJobLines={selectedLines}/>
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import React, {useState} from "react";
|
||||
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Form, notification, Popover, Select, Space} from "antd";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {UPDATE_LINE_BULK_ASSIGN} from "../../graphql/jobs-lines.queries";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({jobid, operation}) =>
|
||||
dispatch(insertAuditTrail({jobid, operation})),
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JoblineBulkAssign);
|
||||
|
||||
export function JoblineBulkAssign({
|
||||
setSelectedLines,
|
||||
selectedLines,
|
||||
insertAuditTrail,
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
currentUser,
|
||||
}) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const {t} = useTranslation();
|
||||
const [assignLines] = useMutation(UPDATE_LINE_BULK_ASSIGN);
|
||||
|
||||
const handleConvert = async (values) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await assignLines({
|
||||
variables: {
|
||||
jobline: {
|
||||
assigned_team: values.assigned_team,
|
||||
},
|
||||
ids: selectedLines.map((l) => l.id),
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("parts_dispatch.errors.creating", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
//Insert the audit trail here.
|
||||
|
||||
const teamName = bodyshop.employee_teams.find(
|
||||
(et) => et.id === values.assigned_team
|
||||
)?.name;
|
||||
|
||||
const hours = selectedLines.reduce(
|
||||
(acc, val) => (acc += val.mod_lb_hrs),
|
||||
0
|
||||
);
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.assignedlinehours(
|
||||
teamName,
|
||||
hours.toFixed(1)
|
||||
),
|
||||
});
|
||||
setSelectedLines([]);
|
||||
setVisible(false);
|
||||
}
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("parts_dispatch.errors.creating", {
|
||||
error: error,
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const popMenu = (
|
||||
<div>
|
||||
<Form layout="vertical" form={form} onFinish={handleConvert}>
|
||||
<Form.Item
|
||||
name={"assigned_team"}
|
||||
label={t("joblines.fields.assigned_team")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
style={{width: 200}}
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
option.props.children
|
||||
.toLowerCase()
|
||||
.indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
>
|
||||
{bodyshop.employee_teams.map((team) => (
|
||||
<Select.Option value={team.id} key={team.id} name={team.name}>
|
||||
{team.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<Button type="danger" onClick={() => form.submit()} loading={loading}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisible(false)}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={visible} content={popMenu}>
|
||||
<Button
|
||||
disabled={selectedLines.length === 0 || jobRO}
|
||||
loading={loading}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{t("joblines.actions.assign_team", {count: selectedLines.length})}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import React, {useState} from "react";
|
||||
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Form, notification, Popover, Select, Space} from "antd";
|
||||
import day from "../../utils/day";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {INSERT_PARTS_DISPATCH} from "../../graphql/parts-dispatch.queries";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import {GenerateDocument} from "../../utils/RenderTemplate";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobLineDispatchButton);
|
||||
|
||||
export function JobLineDispatchButton({
|
||||
setSelectedLines,
|
||||
selectedLines,
|
||||
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
currentUser,
|
||||
}) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const Templates = TemplateList("job_special", {
|
||||
ro_number: job.ro_number,
|
||||
});
|
||||
const {t} = useTranslation();
|
||||
const [dispatchLines] = useMutation(INSERT_PARTS_DISPATCH);
|
||||
|
||||
const handleConvert = async (values) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
//THIS HAS NOT YET BEEN TESTED. START BY FINISHING THIS FUNCTION.
|
||||
const result = await dispatchLines({
|
||||
variables: {
|
||||
partsDispatch: {
|
||||
dispatched_at: day(),
|
||||
employeeid: values.employeeid,
|
||||
jobid: job.id,
|
||||
dispatched_by: currentUser.email,
|
||||
parts_dispatch_lines: {
|
||||
data: selectedLines.map((l) => ({
|
||||
joblineid: l.id,
|
||||
quantity: l.part_qty,
|
||||
})),
|
||||
},
|
||||
},
|
||||
//joblineids: selectedLines.map((l) => l.id),
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("parts_dispatch.errors.creating", {
|
||||
error: result.errors,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
setSelectedLines([]);
|
||||
await GenerateDocument(
|
||||
{
|
||||
name: Templates.parts_dispatch.key,
|
||||
variables: {
|
||||
id: result.data.insert_parts_dispatch_one.id,
|
||||
},
|
||||
},
|
||||
{},
|
||||
"p"
|
||||
);
|
||||
}
|
||||
setVisible(false);
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("parts_dispatch.errors.creating", {
|
||||
error: error,
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const popMenu = (
|
||||
<div>
|
||||
<Form layout="vertical" form={form} onFinish={handleConvert}>
|
||||
<Form.Item
|
||||
name={"employeeid"}
|
||||
label={t("timetickets.fields.employee")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
style={{width: 200}}
|
||||
optionFilterProp="children"
|
||||
filterOption={(input, option) =>
|
||||
option.props.children
|
||||
.toLowerCase()
|
||||
.indexOf(input.toLowerCase()) >= 0
|
||||
}
|
||||
>
|
||||
{bodyshop.employees
|
||||
.filter((emp) => emp.active)
|
||||
.map((emp) => (
|
||||
<Select.Option
|
||||
value={emp.id}
|
||||
key={emp.id}
|
||||
name={`${emp.first_name} ${emp.last_name}`}
|
||||
>
|
||||
{`${emp.first_name} ${emp.last_name}`}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<Button
|
||||
type="danger"
|
||||
onClick={() => form.submit()}
|
||||
loading={loading}
|
||||
disabled={selectedLines.length === 0}
|
||||
>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisible(false)}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={visible} content={popMenu}>
|
||||
<Button
|
||||
disabled={selectedLines.length === 0 || jobRO}
|
||||
loading={loading}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{t("joblines.actions.dispatchparts", {count: selectedLines.length})}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {notification, Select} from "antd";
|
||||
import {notification, Select, Space} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
@@ -77,7 +77,10 @@ export function JobLineLocationPopup({bodyshop, jobline, disabled}) {
|
||||
style={{width: "100%", minHeight: "2rem", cursor: "pointer"}}
|
||||
onClick={() => !disabled && setEditing(true)}
|
||||
>
|
||||
{jobline.location}
|
||||
<Space wrap>
|
||||
{jobline.location}
|
||||
{jobline.parts_dispatch_lines?.length > 0 && "-Disp"}
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import {notification, Select} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {UPDATE_JOB_LINE} from "../../graphql/jobs-lines.queries";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import {insertAuditTrail} from "../../redux/application/application.actions";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
insertAuditTrail: ({jobid, operation}) =>
|
||||
dispatch(insertAuditTrail({jobid, operation})),
|
||||
});
|
||||
|
||||
export function JoblineTeamAssignment({
|
||||
bodyshop,
|
||||
jobline,
|
||||
disabled,
|
||||
jobId,
|
||||
insertAuditTrail,
|
||||
}) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [assignedTeam, setAssignedTeam] = useState(jobline.assigned_team);
|
||||
const [updateJob] = useMutation(UPDATE_JOB_LINE);
|
||||
const {t} = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (editing) setAssignedTeam(jobline.assigned_team);
|
||||
}, [editing, jobline.assigned_team]);
|
||||
|
||||
const handleChange = (e) => {
|
||||
setAssignedTeam(e);
|
||||
};
|
||||
|
||||
const handleSave = async (e) => {
|
||||
setLoading(true);
|
||||
const result = await updateJob({
|
||||
variables: {
|
||||
lineId: jobline.id,
|
||||
line: {assigned_team: assignedTeam},
|
||||
},
|
||||
});
|
||||
|
||||
if (!!!result.errors) {
|
||||
notification["success"]({message: t("joblines.successes.saved")});
|
||||
//insert the audit trail here.
|
||||
const teamName = bodyshop.employee_teams.find(
|
||||
(et) => et.id === assignedTeam
|
||||
)?.name;
|
||||
insertAuditTrail({
|
||||
jobid: jobId,
|
||||
operation: AuditTrailMapping.assignedlinehours(
|
||||
teamName,
|
||||
jobline.mod_lb_hrs
|
||||
),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("joblines.errors.saving", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
if (editing)
|
||||
return (
|
||||
<div>
|
||||
<LoadingSpinner loading={loading}>
|
||||
<Select
|
||||
autoFocus
|
||||
allowClear
|
||||
dropdownMatchSelectWidth={100}
|
||||
value={assignedTeam}
|
||||
onSelect={handleChange}
|
||||
onBlur={handleSave}
|
||||
onClear={() => handleChange(null)}
|
||||
>
|
||||
{Object.values(bodyshop.employee_teams).map((s, idx) => (
|
||||
<Select.Option key={idx} value={s.id}>
|
||||
{s.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</LoadingSpinner>
|
||||
</div>
|
||||
);
|
||||
|
||||
const team = bodyshop.employee_teams.find(
|
||||
(tm) => tm.id === jobline.assigned_team
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{width: "100%", minHeight: "1rem", cursor: "pointer"}}
|
||||
onClick={() => !disabled && setEditing(true)}
|
||||
>
|
||||
{team?.name}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JoblineTeamAssignment);
|
||||
@@ -0,0 +1,18 @@
|
||||
import {Alert} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export default function JobProfileDataWarning({job}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
let missingProfileInfo =
|
||||
Object.keys(job.cieca_pft).length === 0 ||
|
||||
Object.keys(job.cieca_pfl).length === 0 ||
|
||||
Object.keys(job.materials).length === 0;
|
||||
|
||||
if (missingProfileInfo)
|
||||
return (
|
||||
<Alert type="error" message={t("jobs.labels.missingprofileinfo")}></Alert>
|
||||
);
|
||||
return null;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import React, {useState} from "react";
|
||||
import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-table.component";
|
||||
import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
|
||||
import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function JobReconciliationModalComponent({job, bills}) {
|
||||
const jobLineState = useState([]);
|
||||
@@ -17,14 +18,25 @@ export default function JobReconciliationModalComponent({job, bills}) {
|
||||
)
|
||||
.flat() || [];
|
||||
|
||||
const jobLineData = job.joblines.filter(
|
||||
(j) =>
|
||||
(j.part_type !== null && j.part_type !== "PAE") ||
|
||||
(j.line_desc &&
|
||||
j.line_desc.toLowerCase().includes("towing") &&
|
||||
j.lbr_op === "OP13") ||
|
||||
j.db_ref === "936004" //ADD SHIPPING LINE.
|
||||
);
|
||||
const filterFunction = InstanceRenderManager({
|
||||
imex: (j) =>
|
||||
(j.part_type !== null && j.part_type !== "PAE") ||
|
||||
(j.line_desc &&
|
||||
j.line_desc.toLowerCase().includes("towing") &&
|
||||
j.lbr_op === "OP13") ||
|
||||
j.db_ref === "936004", //ADD SHIPPING LINE.
|
||||
rome: (j) =>
|
||||
(j.part_type !== "PAE" &&
|
||||
j.act_price !== 0 &&
|
||||
j.part_qty !== 0) ||
|
||||
j.misc_amt !== 0 ||
|
||||
(j.line_desc &&
|
||||
j.line_desc.toLowerCase().includes("towing") &&
|
||||
j.lbr_op === "OP13") ||
|
||||
j.db_ref === "936004", //ADD SHIPPING LINE.
|
||||
});
|
||||
|
||||
const jobLineData = job.joblines.filter((j) => filterFunction(j));
|
||||
|
||||
return (
|
||||
<div style={{flex: 1, display: "flex", flexDirection: "column"}}>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import {Button, notification} from "antd";
|
||||
import axios from "axios";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
export default function JobSendPartPriceChangeComponent({job}) {
|
||||
const {t} = useTranslation();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleClick = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const ppcData = await axios.post("/job/ppc", {jobid: job.id});
|
||||
await axios.post("http://localhost:1337/ppc/", ppcData.data);
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("jobs.errors.partspricechange", {
|
||||
error: JSON.stringify(error),
|
||||
}),
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleClick} loading={loading}>
|
||||
{t("jobs.actions.sendpartspricechange")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -67,7 +67,8 @@ export function JobsTotalsTableComponent({jobRO, currentUser, job}) {
|
||||
<JobTotalsTableTotals job={job}/>
|
||||
</Card>
|
||||
</Col>
|
||||
{currentUser.email.includes("@imex.") && (
|
||||
{(currentUser.email.includes("@imex.") ||
|
||||
currentUser.email.includes("@rome.")) && (
|
||||
<Col span={24}>
|
||||
<Card title="DEVELOPMENT USE ONLY">
|
||||
<JobCalculateTotals job={job} disabled={jobRO}/>
|
||||
|
||||
@@ -4,6 +4,7 @@ import React, {useMemo, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import {alphaSort} from "../../utils/sorters";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function JobTotalsTableLabor({job}) {
|
||||
const {t} = useTranslation();
|
||||
@@ -103,9 +104,11 @@ export default function JobTotalsTableLabor({job}) {
|
||||
<>
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<strong>{t("jobs.labels.labor_rates_subtotal")}</strong>
|
||||
<strong>
|
||||
{t("jobs.labels.labor_rates_subtotal")}
|
||||
</strong>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell/>
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell>
|
||||
{(
|
||||
job.job_totals.rates.mapa.hours +
|
||||
@@ -114,7 +117,9 @@ export default function JobTotalsTableLabor({job}) {
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
<strong>
|
||||
{Dinero(job.job_totals.rates.rates_subtotal).toFormat()}
|
||||
{Dinero(
|
||||
job.job_totals.rates.rates_subtotal
|
||||
).toFormat()}
|
||||
</strong>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
@@ -122,13 +127,26 @@ export default function JobTotalsTableLabor({job}) {
|
||||
<Table.Summary.Cell>
|
||||
<Space>
|
||||
{t("jobs.labels.mapa")}
|
||||
{job.materials &&
|
||||
job.materials.mapa &&
|
||||
job.materials.mapa.cal_maxdlr &&
|
||||
job.materials.mapa.cal_maxdlr > 0 &&
|
||||
t("jobs.labels.threshhold", {
|
||||
amount: job.materials.mapa.cal_maxdlr,
|
||||
})}
|
||||
{InstanceRenderManager({
|
||||
imex:
|
||||
job.materials &&
|
||||
job.materials.mapa &&
|
||||
job.materials.mapa.cal_maxdlr &&
|
||||
job.materials.mapa.cal_maxdlr > 0 &&
|
||||
t("jobs.labels.threshhold", {
|
||||
amount: job.materials.mapa
|
||||
.cal_maxdlr,
|
||||
}),
|
||||
rome:
|
||||
job.materials &&
|
||||
job.materials.MAPA &&
|
||||
job.materials.MAPA.cal_maxdlr !==
|
||||
undefined &&
|
||||
t("jobs.labels.threshhold", {
|
||||
amount: job.materials.MAPA
|
||||
.cal_maxdlr,
|
||||
}),
|
||||
})}
|
||||
</Space>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
@@ -147,13 +165,24 @@ export default function JobTotalsTableLabor({job}) {
|
||||
<Table.Summary.Cell>
|
||||
<Space wrap>
|
||||
{t("jobs.labels.mash")}
|
||||
{job.materials &&
|
||||
job.materials.mash &&
|
||||
job.materials.mash.cal_maxdlr &&
|
||||
job.materials.mash.cal_maxdlr > 0 &&
|
||||
t("jobs.labels.threshhold", {
|
||||
amount: job.materials.mash.cal_maxdlr,
|
||||
})}
|
||||
{
|
||||
InstanceRenderManager({
|
||||
imex:job.materials &&
|
||||
job.materials.mash &&
|
||||
job.materials.mash.cal_maxdlr &&
|
||||
job.materials.mash.cal_maxdlr > 0 &&
|
||||
t("jobs.labels.threshhold", {
|
||||
amount: job.materials.mash.cal_maxdlr,
|
||||
}),
|
||||
rome: job.materials &&
|
||||
job.materials.MASH &&
|
||||
job.materials.MASH.cal_maxdlr !==
|
||||
undefined &&
|
||||
t("jobs.labels.threshhold", {
|
||||
amount: job.materials.MASH.cal_maxdlr,
|
||||
})
|
||||
})
|
||||
}
|
||||
</Space>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell align="right">
|
||||
@@ -172,11 +201,13 @@ export default function JobTotalsTableLabor({job}) {
|
||||
<Table.Summary.Cell>
|
||||
<strong>{t("jobs.labels.rates_subtotal")}</strong>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell/>
|
||||
<Table.Summary.Cell/>
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell align="right">
|
||||
<strong>
|
||||
{Dinero(job.job_totals.rates.subtotal).toFormat()}
|
||||
{Dinero(
|
||||
job.job_totals.rates.subtotal
|
||||
).toFormat()}
|
||||
</strong>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
|
||||
@@ -11,6 +11,22 @@ export default function JobTotalsTableParts({job}) {
|
||||
filteredInfo: {},
|
||||
});
|
||||
|
||||
const insuranceAdjustments = useMemo(() => {
|
||||
if (!job.job_totals) return [];
|
||||
if (!job.job_totals?.parts?.adjustments) return [];
|
||||
const adjs = [];
|
||||
Object.keys(job.job_totals?.parts?.adjustments).forEach((key) => {
|
||||
if (Dinero(job.job_totals?.parts?.adjustments[key]).getAmount() !== 0) {
|
||||
adjs.push({
|
||||
id: key,
|
||||
amount: Dinero(job.job_totals.parts.adjustments[key]),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return adjs;
|
||||
}, [job.job_totals]);
|
||||
|
||||
const data = useMemo(() => {
|
||||
return Object.keys(job.job_totals.parts.parts.list)
|
||||
.filter(
|
||||
@@ -74,11 +90,11 @@ export default function JobTotalsTableParts({job}) {
|
||||
<Table.Summary.Cell>
|
||||
{t("jobs.labels.prt_dsmk_total")}
|
||||
</Table.Summary.Cell>
|
||||
|
||||
<Table.Summary.Cell align="right">
|
||||
{Dinero(job.job_totals.parts.parts.prt_dsmk_total).toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<strong>{t("jobs.labels.partstotal")}</strong>
|
||||
@@ -90,6 +106,26 @@ export default function JobTotalsTableParts({job}) {
|
||||
</strong>
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
{
|
||||
//TODO:AIO This shoudl only be in the US version. need to verify whether this causes problems for the CA version.
|
||||
insuranceAdjustments.length > 0 && (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell colSpan={24}>
|
||||
{t("jobs.labels.profileadjustments")}
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
)}
|
||||
{insuranceAdjustments.map((adj, idx) => (
|
||||
<Table.Summary.Row key={idx}>
|
||||
<Table.Summary.Cell>
|
||||
{t(`jobs.fields.${adj.id.toLowerCase()}`)}
|
||||
</Table.Summary.Cell>
|
||||
|
||||
<Table.Summary.Cell align="right">
|
||||
{adj.amount.toFormat()}
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -29,26 +29,109 @@ export function JobTotalsTableTotals({bodyshop, job}) {
|
||||
total: job.job_totals.totals.subtotal,
|
||||
bold: true,
|
||||
},
|
||||
{
|
||||
key: t("jobs.labels.local_tax_amt"),
|
||||
total: job.job_totals.totals.local_tax,
|
||||
},
|
||||
{
|
||||
key: t("jobs.labels.state_tax_amt"),
|
||||
total: job.job_totals.totals.state_tax,
|
||||
},
|
||||
...(bodyshop.region_config === "CA_BC"
|
||||
|
||||
...(job.job_totals.totals.us_sales_tax_breakdown
|
||||
? [
|
||||
{
|
||||
key: t("jobs.fields.ca_bc_pvrt"),
|
||||
total: job.job_totals.additional.pvrt,
|
||||
key: `${
|
||||
bodyshop.md_responsibility_centers.taxes.tax_ty1?.tax_type1 ||
|
||||
"T1"
|
||||
} - ${[
|
||||
job.cieca_pft.ty1_rate1,
|
||||
job.cieca_pft.ty1_rate2,
|
||||
job.cieca_pft.ty1_rate3,
|
||||
job.cieca_pft.ty1_rate4,
|
||||
job.cieca_pft.ty1_rate5,
|
||||
]
|
||||
.filter((i) => i > 0)
|
||||
.join(", ")}%`,
|
||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty1Tax,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: t("jobs.labels.federal_tax_amt"),
|
||||
total: job.job_totals.totals.federal_tax,
|
||||
},
|
||||
{
|
||||
key: `${
|
||||
bodyshop.md_responsibility_centers.taxes.tax_ty2?.tax_type2 ||
|
||||
"T2"
|
||||
} - ${[
|
||||
job.cieca_pft.ty2_rate1,
|
||||
job.cieca_pft.ty2_rate2,
|
||||
job.cieca_pft.ty2_rate3,
|
||||
job.cieca_pft.ty2_rate4,
|
||||
job.cieca_pft.ty2_rate5,
|
||||
]
|
||||
.filter((i) => i > 0)
|
||||
.join(", ")}%`,
|
||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty2Tax,
|
||||
},
|
||||
{
|
||||
key: `${
|
||||
bodyshop.md_responsibility_centers.taxes.tax_ty3?.tax_type3 ||
|
||||
"T3"
|
||||
} - ${[
|
||||
job.cieca_pft.ty3_rate1,
|
||||
job.cieca_pft.ty3_rate2,
|
||||
job.cieca_pft.ty3_rate3,
|
||||
job.cieca_pft.ty3_rate4,
|
||||
job.cieca_pft.ty3_rate5,
|
||||
]
|
||||
.filter((i) => i > 0)
|
||||
.join(", ")}%`,
|
||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty3Tax,
|
||||
},
|
||||
{
|
||||
key: `${
|
||||
bodyshop.md_responsibility_centers.taxes.tax_ty4?.tax_type4 ||
|
||||
"T4"
|
||||
} - ${[
|
||||
job.cieca_pft.ty4_rate1,
|
||||
job.cieca_pft.ty4_rate2,
|
||||
job.cieca_pft.ty4_rate3,
|
||||
job.cieca_pft.ty4_rate4,
|
||||
job.cieca_pft.ty4_rate5,
|
||||
]
|
||||
.filter((i) => i > 0)
|
||||
.join(", ")}%`,
|
||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax,
|
||||
},
|
||||
{
|
||||
key: `${
|
||||
bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 ||
|
||||
"TT"
|
||||
} - ${[
|
||||
job.cieca_pft.ty5_rate1,
|
||||
job.cieca_pft.ty5_rate2,
|
||||
job.cieca_pft.ty5_rate3,
|
||||
job.cieca_pft.ty5_rate4,
|
||||
job.cieca_pft.ty5_rate5,
|
||||
]
|
||||
.filter((i) => i > 0)
|
||||
.join(", ")}%`,
|
||||
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax,
|
||||
},
|
||||
{
|
||||
key: t("jobs.labels.total_sales_tax"),
|
||||
bold: true,
|
||||
total: Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty1Tax)
|
||||
.add(
|
||||
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty2Tax)
|
||||
)
|
||||
.add(
|
||||
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)
|
||||
)
|
||||
.add(
|
||||
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)
|
||||
)
|
||||
.add(
|
||||
Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)
|
||||
).toJSON(),
|
||||
},
|
||||
].filter((item) => item.total.amount !== 0)
|
||||
: [
|
||||
{
|
||||
key: t("jobs.labels.state_tax_amt"),
|
||||
total: job.job_totals.totals.state_tax,
|
||||
},
|
||||
]),
|
||||
|
||||
{
|
||||
key: t("jobs.labels.total_repairs"),
|
||||
total: job.job_totals.totals.total_repairs,
|
||||
@@ -58,10 +141,10 @@ export function JobTotalsTableTotals({bodyshop, job}) {
|
||||
key: t("jobs.fields.ded_amt"),
|
||||
total: job.job_totals.totals.custPayable.deductible,
|
||||
},
|
||||
{
|
||||
key: t("jobs.fields.federal_tax_payable"),
|
||||
total: job.job_totals.totals.custPayable.federal_tax,
|
||||
},
|
||||
// {
|
||||
// key: t("jobs.fields.federal_tax_payable"),
|
||||
// total: job.job_totals.totals.custPayable.federal_tax,
|
||||
// },
|
||||
{
|
||||
key: t("jobs.fields.other_amount_payable"),
|
||||
total: job.job_totals.totals.custPayable.other_customer_amount,
|
||||
@@ -82,7 +165,7 @@ export function JobTotalsTableTotals({bodyshop, job}) {
|
||||
bold: true,
|
||||
},
|
||||
];
|
||||
}, [job.job_totals, t, bodyshop.region_config]);
|
||||
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
|
||||
@@ -2,8 +2,7 @@ import {useMutation} from "@apollo/client";
|
||||
import {Button, Space, notification} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
import dayjs from "../../utils/day";
|
||||
import dayjs from '../../utils/day';
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {notification, Switch} from "antd";
|
||||
import { notification, Switch} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
|
||||
@@ -25,7 +25,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
//Found a relevant matching line. Add it to lines to update.
|
||||
linesToUpdate.push({
|
||||
id: existingLines[matchingIndex].id,
|
||||
newData: {...newLine, removed: false},
|
||||
newData: {...newLine, removed: false, act_price_before_ppc: null},
|
||||
});
|
||||
|
||||
//Splice out item we found for performance.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {gql, useApolloClient, useLazyQuery, useMutation, useQuery,} from "@apollo/client";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Col, notification, Row} from "antd";
|
||||
import {Button, Col, notification, Row} from "antd";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, {useCallback, useEffect, useState} from "react";
|
||||
@@ -32,6 +32,7 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
|
||||
import {GetSupplementDelta} from "./jobs-available-supplement.estlines.util";
|
||||
import HeaderFields from "./jobs-available-supplement.headerfields";
|
||||
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -82,14 +83,15 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
const modalSearchState = useState("");
|
||||
|
||||
//Import Scenario
|
||||
const onOwnerFindModalOk = async () => {
|
||||
const onOwnerFindModalOk = async (lazyData) => {
|
||||
logImEXEvent("job_import_new");
|
||||
|
||||
setOwnerModalVisible(false);
|
||||
|
||||
setInsertLoading(true);
|
||||
|
||||
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
||||
const estData = replaceEmpty(
|
||||
lazyData?.available_jobs_by_pk || estDataRaw.data.available_jobs_by_pk
|
||||
);
|
||||
|
||||
if (!(estData && estData.est_data)) {
|
||||
//We don't have the right data. Error!
|
||||
@@ -99,17 +101,24 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
});
|
||||
return;
|
||||
}
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(estData.est_data, bodyshop);
|
||||
// if (process.env.REACT_APP_COUNTRY === "USA") {
|
||||
//Massage the CCC file set to remove duplicate UNQ_SEQ.
|
||||
InstanceRenderManager({executeFunction:true,rome: ResolveCCCLineIssues(estData.est_data, bodyshop) })
|
||||
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: {
|
||||
...estData.est_data,
|
||||
joblines: estData.est_data.joblines.data,
|
||||
},
|
||||
})
|
||||
).data;
|
||||
// } else {
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
//TODO:AIO Check that the async function is actually waiting before moving on.
|
||||
InstanceRenderManager({executeFunction: true, imex: await CheckTaxRates(estData.est_data, bodyshop), rome: await CheckTaxRatesUSA(estData.est_data, bodyshop)})
|
||||
|
||||
// }
|
||||
// const newTotals = (
|
||||
// await Axios.post("/job/totals", {
|
||||
// job: {
|
||||
// ...estData.est_data,
|
||||
// joblines: estData.est_data.joblines.data,
|
||||
// },
|
||||
// })
|
||||
// ).data;
|
||||
|
||||
let existingVehicles;
|
||||
if (estData.est_data.v_vin) {
|
||||
@@ -124,9 +133,9 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
|
||||
const newJob = {
|
||||
...estData.est_data,
|
||||
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
|
||||
job_totals: newTotals,
|
||||
// clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
|
||||
// owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
|
||||
// job_totals: newTotals,
|
||||
date_open: dayjs(),
|
||||
status: bodyshop.md_ro_statuses.default_imported,
|
||||
notes: {
|
||||
@@ -150,17 +159,23 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
delete newJob.vehicle;
|
||||
}
|
||||
|
||||
if (typeof newJob.kmin === "string") {
|
||||
newJob.kmin = null;
|
||||
}
|
||||
|
||||
try {
|
||||
const r = await insertNewJob({
|
||||
variables: {
|
||||
job: newJob,
|
||||
},
|
||||
});
|
||||
await Axios.post("/job/totalsssu", {
|
||||
id: r.data.insert_jobs.returning[0].id,
|
||||
});
|
||||
|
||||
if (CriticalPartsScanning.treatment === "on") {
|
||||
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
|
||||
}
|
||||
|
||||
notification["success"]({
|
||||
message: t("jobs.successes.created"),
|
||||
onClick: () => {
|
||||
@@ -174,7 +189,7 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
operation: AuditTrailMapping.jobimported(),
|
||||
});
|
||||
|
||||
deleteJob({
|
||||
await deleteJob({
|
||||
variables: {id: estData.id},
|
||||
}).then((r) => {
|
||||
refetch();
|
||||
@@ -182,18 +197,15 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
});
|
||||
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
} catch (err) {
|
||||
} catch (r) {
|
||||
//error while inserting
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.creating", {error: err.message}),
|
||||
});
|
||||
refetch().catch(e => {
|
||||
console.error(`Something went wrong in jobs available table container - ${err.message || ''}`)
|
||||
message: t("jobs.errors.creating", {error: r.message}),
|
||||
});
|
||||
refetch();
|
||||
setInsertLoading(false);
|
||||
setPartsQueueToggle(bodyshop.md_functionality_toggles.parts_queue_toggle);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//Supplement scenario
|
||||
@@ -215,7 +227,9 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
//create upsert job
|
||||
let supp = replaceEmpty({...estData.est_data});
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(supp, bodyshop);
|
||||
|
||||
InstanceRenderManager({executeFunction:true, imex: await CheckTaxRates(supp, bodyshop), rome: await CheckTaxRatesUSA(supp, bodyshop)})
|
||||
InstanceRenderManager({executeFunction:true ,rome: ResolveCCCLineIssues(supp, bodyshop) })
|
||||
|
||||
delete supp.owner;
|
||||
delete supp.vehicle;
|
||||
@@ -391,6 +405,28 @@ export function JobsAvailableContainer({bodyshop, currentUser, insertAuditTrail,
|
||||
partsQueueToggle={partsQueueToggle}
|
||||
setPartsQueueToggle={setPartsQueueToggle}
|
||||
/>
|
||||
{
|
||||
|
||||
|
||||
// currentUser.email.includes("@rome.") ||
|
||||
// currentUser.email.includes("@imex.") ? (
|
||||
// <Button
|
||||
// onClick={async () => {
|
||||
// for (const record of data.available_jobs) {
|
||||
// //Query the data
|
||||
// console.log("Start Job", record.id);
|
||||
// const {data} = await loadEstData({
|
||||
// variables: {id: record.id},
|
||||
// });
|
||||
// console.log("Query has been awaited and is complete");
|
||||
// await onOwnerFindModalOk(data);
|
||||
// }
|
||||
// }}
|
||||
// >
|
||||
// Add all jobs as new.
|
||||
// </Button>
|
||||
// ) : null
|
||||
}
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<JobsAvailableTableComponent
|
||||
@@ -422,116 +458,159 @@ function replaceEmpty(someObj, replaceValue = null) {
|
||||
return JSON.parse(temp);
|
||||
}
|
||||
|
||||
async function CheckTaxRatesUSA(estData,bodyshop){
|
||||
if (!estData.parts_tax_rates?.PAM) {
|
||||
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function CheckTaxRates(estData, bodyshop) {
|
||||
//LKQ Check
|
||||
if (
|
||||
!estData.parts_tax_rates?.PAL ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAL ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAL) {
|
||||
estData.parts_tax_rates.PAL = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAL",
|
||||
};
|
||||
}
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAL) {
|
||||
estData.parts_tax_rates.PAL = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAL",
|
||||
};
|
||||
}
|
||||
estData.parts_tax_rates.PAL.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
}
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
//PAC Check
|
||||
if (
|
||||
!estData.parts_tax_rates?.PAC ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAC ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAC) {
|
||||
estData.parts_tax_rates.PAC = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAC",
|
||||
};
|
||||
}
|
||||
estData.parts_tax_rates.PAC.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAC.prt_tax_in = true;
|
||||
}
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAC) {
|
||||
estData.parts_tax_rates.PAC = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAC",
|
||||
};
|
||||
}
|
||||
//PAM Check
|
||||
if (
|
||||
!estData.parts_tax_rates?.PAM ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
|
||||
estData.parts_tax_rates.PAC.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAC.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
//PAM Check
|
||||
if (
|
||||
!estData.parts_tax_rates?.PAM ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAM) {
|
||||
estData.parts_tax_rates.PAM = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAM",
|
||||
};
|
||||
}
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAM) {
|
||||
estData.parts_tax_rates.PAM = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAM",
|
||||
};
|
||||
}
|
||||
estData.parts_tax_rates.PAM.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAM.prt_tax_in = true;
|
||||
}
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAM.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!estData.parts_tax_rates?.PAR ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAR ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAR) {
|
||||
estData.parts_tax_rates.PAR = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAR",
|
||||
};
|
||||
}
|
||||
if (res) {
|
||||
if (!estData.parts_tax_rates.PAR) {
|
||||
estData.parts_tax_rates.PAR = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAR",
|
||||
};
|
||||
}
|
||||
estData.parts_tax_rates.PAR.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAR.prt_tax_in = true;
|
||||
}
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.parts_tax_rates.PAR.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
|
||||
//Currently limited to SK shops only.
|
||||
//if (bodyshop.region_config === "CA_SK") {
|
||||
estData.joblines.data.forEach((jl, index) => {
|
||||
if (
|
||||
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
|
||||
jl.lbr_op !== "OP11"
|
||||
) {
|
||||
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
||||
}
|
||||
estData.joblines.data.forEach((jl, index) => {
|
||||
if (
|
||||
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
|
||||
jl.lbr_op !== "OP11"
|
||||
) {
|
||||
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
||||
}
|
||||
|
||||
//Set markup lines and tax lines as taxable.
|
||||
//900510 is a mark up. 900510 is a discount.
|
||||
if (jl.db_ref === "900510") {
|
||||
estData.joblines.data[index].tax_part = true;
|
||||
}
|
||||
});
|
||||
//Set markup lines and tax lines as taxable.
|
||||
//900510 is a mark up. 900510 is a discount.
|
||||
if (jl.db_ref === "900510") {
|
||||
estData.joblines.data[index].tax_part = true;
|
||||
}
|
||||
});
|
||||
//}
|
||||
}
|
||||
function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
//Find all misc amounts, populate them to the act price.
|
||||
//TODO Ensure that this doesnt get violated
|
||||
//This needs to be done before cleansing unq_seq since some misc prices could move over.
|
||||
estData.joblines.data.forEach((line) => {
|
||||
if (line.misc_amt && line.misc_amt !== 0) {
|
||||
line.act_price = line.act_price + line.misc_amt;
|
||||
line.tax_part = !!line.misc_tax;
|
||||
}
|
||||
});
|
||||
|
||||
//Generate the list of duplicated UNQ_SEQ that will feed into the next section to scrub the lines.
|
||||
const unqSeqHash = _.groupBy(estData.joblines.data, "unq_seq");
|
||||
const duplicatedUnqSeq = Object.keys(unqSeqHash).filter(
|
||||
(key) => unqSeqHash[key].length > 1
|
||||
);
|
||||
|
||||
duplicatedUnqSeq.forEach((unq_seq) => {
|
||||
//Keys are strings, convert to int.
|
||||
const int_unq_seq = parseInt(unq_seq);
|
||||
|
||||
//When line splitting, the first line is always the non-refinish line. We will keep it as is.
|
||||
//We will cleanse the second line, which is always the next line.
|
||||
const nonRefLineIndex = estData.joblines.data.findIndex(
|
||||
(line) => line.unq_seq === int_unq_seq
|
||||
);
|
||||
estData.joblines.data[nonRefLineIndex + 1] = {
|
||||
...estData.joblines.data[nonRefLineIndex + 1],
|
||||
part_type: null,
|
||||
act_price: 0,
|
||||
db_price: 0,
|
||||
prt_dsmk_p: 0,
|
||||
prt_dsmk_m: 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ export function JobsCloseAutoAllocate({bodyshop, joblines, form, disabled}) {
|
||||
ret.profitcenter_part = defaults.profits["MAPA"];
|
||||
} else if (lineDesc.includes("ats amount")) {
|
||||
ret.profitcenter_part = defaults.profits["ATS"];
|
||||
} else if (jl.act_price > 0) {
|
||||
ret.profitcenter_part = defaults.profits["PAO"];
|
||||
} else {
|
||||
ret.profitcenter_part = null;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ import {createStructuredSelector} from "reselect";
|
||||
import {auth, logImEXEvent} from "../../firebase/firebase.utils";
|
||||
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
|
||||
import {UPDATE_JOB} from "../../graphql/jobs.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
|
||||
@@ -10,8 +10,15 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import FormItemPhone, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component";
|
||||
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
|
||||
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
|
||||
|
||||
import JobsDetailRatesLabor from "../jobs-detail-rates/jobs-detail-rates.labor.component";
|
||||
import JobsDetailRatesMaterials from "../jobs-detail-rates/jobs-detail-rates.materials.component";
|
||||
import JobsDetailRatesOther from "../jobs-detail-rates/jobs-detail-rates.other.component";
|
||||
import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.component";
|
||||
|
||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -257,26 +264,31 @@ export function JobsCreateJobsInfo({bodyshop, form, selected}) {
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.state_tax_rate")}
|
||||
name="state_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{
|
||||
InstanceRenderManager({imex:
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.state_tax_rate")}
|
||||
name="state_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
})
|
||||
}
|
||||
|
||||
<LayoutFormRow>
|
||||
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
|
||||
<CurrencyInput/>
|
||||
@@ -354,7 +366,15 @@ export function JobsCreateJobsInfo({bodyshop, form, selected}) {
|
||||
expanded
|
||||
required={selected && true}
|
||||
form={form}
|
||||
/>
|
||||
/>{
|
||||
InstanceRenderManager({rome:
|
||||
<>
|
||||
<JobsDetailRatesLabor form={form}/>
|
||||
<JobsDetailRatesMaterials form={form}/>
|
||||
<JobsDetailRatesOther form={form}/>
|
||||
<JobsDetailRatesTaxes form={form}/>
|
||||
</>})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,5 +43,4 @@ export default function AddToProduction(
|
||||
|
||||
//insert the new job. call the callback with the returned ID when done.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(setModalContext({context: context, modal: "cardPayment"})),
|
||||
insertAuditTrail: ({jobid, operation}) =>
|
||||
dispatch(insertAuditTrail({jobid, operation})),
|
||||
setTimeTicketTaskContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "timeTicketTask"})),
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text)),
|
||||
@@ -70,7 +72,8 @@ export function JobsDetailHeaderActions({
|
||||
insertAuditTrail,
|
||||
setEmailOptions,
|
||||
openChatByPhone,
|
||||
setMessage
|
||||
setMessage,
|
||||
setTimeTicketTaskContext,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -678,7 +681,23 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
}];
|
||||
|
||||
if (bodyshop.md_tasks_presets.enable_tasks) {
|
||||
menuItems.push({
|
||||
key: 'claimtimetickettasks',
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
onClick: () => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: {jobid: job.id},
|
||||
});
|
||||
},
|
||||
label: t("timetickets.actions.claimtasks")
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push(
|
||||
{
|
||||
key: 'enterpayments',
|
||||
disabled: !job.converted,
|
||||
@@ -691,7 +710,7 @@ export function JobsDetailHeaderActions({
|
||||
context: {jobid: job.id},
|
||||
});
|
||||
}
|
||||
}];
|
||||
});
|
||||
|
||||
if (ImEXPay.treatment === "on") {
|
||||
menuItems.push({
|
||||
|
||||
@@ -59,7 +59,7 @@ export default async function DuplicateJob(
|
||||
|
||||
//insert the new job. call the callback with the returned ID when done.
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
export async function CreateIouForJob(
|
||||
|
||||
@@ -5,9 +5,13 @@ import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||
import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailLaborContainer);
|
||||
@@ -48,6 +52,7 @@ const adjSpan = {
|
||||
};
|
||||
|
||||
export function JobsDetailLaborContainer({
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
jobId,
|
||||
@@ -58,6 +63,13 @@ export function JobsDetailLaborContainer({
|
||||
techConsole,
|
||||
adjustments,
|
||||
}) {
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...ticketSpan}>
|
||||
@@ -70,14 +82,28 @@ export function JobsDetailLaborContainer({
|
||||
jobId={jobId}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...adjSpan}>
|
||||
<LaborAllocationsTableComponent
|
||||
jobId={jobId}
|
||||
joblines={joblines}
|
||||
timetickets={timetickets}
|
||||
adjustments={adjustments}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
{Enhanced_Payroll.treatment === "on" ? (
|
||||
<Col {...adjSpan}>
|
||||
<PayrollLaborAllocationsTable
|
||||
jobId={jobId}
|
||||
joblines={joblines}
|
||||
timetickets={timetickets}
|
||||
refetch={refetch}
|
||||
adjustments={adjustments}
|
||||
/>
|
||||
</Col>
|
||||
) : (
|
||||
<Col {...adjSpan}>
|
||||
<LaborAllocationsTableComponent
|
||||
jobId={jobId}
|
||||
joblines={joblines}
|
||||
timetickets={timetickets}
|
||||
refetch={refetch}
|
||||
adjustments={adjustments}
|
||||
/>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,12 +6,14 @@ import BillsListTable from "../bills-list-table/bills-list-table.component";
|
||||
import JobBillsTotal from "../job-bills-total/job-bills-total.component";
|
||||
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
|
||||
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
|
||||
import PartsDispatchTable from "../parts-dispatch-table/parts-dispatch-table.component";
|
||||
|
||||
export default function JobsDetailPliComponent({
|
||||
job,
|
||||
billsQuery,
|
||||
handleBillOnRowClick,
|
||||
handlePartsOrderOnRowClick,
|
||||
handlePartsDispatchOnRowClick,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
@@ -43,6 +45,13 @@ export default function JobsDetailPliComponent({
|
||||
billsQuery={billsQuery}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<PartsDispatchTable
|
||||
job={job}
|
||||
handleOnRowClick={handlePartsDispatchOnRowClick}
|
||||
billsQuery={billsQuery}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -39,12 +39,24 @@ export default function JobsDetailPliContainer({job}) {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePartsDispatchOnRowClick = (record) => {
|
||||
if (record) {
|
||||
if (record.id) {
|
||||
search.partsdispatchid = record.id;
|
||||
history.push({search: queryString.stringify(search)});
|
||||
}
|
||||
} else {
|
||||
delete search.partsdispatchid;
|
||||
history.push({search: queryString.stringify(search)});
|
||||
}
|
||||
};
|
||||
return (
|
||||
<JobsDetailPliComponent
|
||||
job={job}
|
||||
billsQuery={billsQuery}
|
||||
handleBillOnRowClick={handleBillOnRowClick}
|
||||
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
|
||||
handlePartsDispatchOnRowClick={handlePartsDispatchOnRowClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
|
||||
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
|
||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
import JobsDetailRatesLabor from "./jobs-detail-rates.labor.component";
|
||||
import JobsDetailRatesMaterials from "./jobs-detail-rates.materials.component";
|
||||
import JobsDetailRatesOther from "./jobs-detail-rates.other.component";
|
||||
import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
|
||||
import JobsDetailRatesTaxes from "./jobs-detail-rates.taxes.component";
|
||||
import JobsDetailRatesProfileOVerride from "./jobs-detail-rates.profile-override.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -111,41 +117,44 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
|
||||
}}
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
<FormRow>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.state_tax_rate")}
|
||||
name="state_tax_rate"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={1}
|
||||
precision={2}
|
||||
disabled={jobRO}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.ca_gst_registrant")}
|
||||
name="ca_gst_registrant"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</FormRow>
|
||||
{
|
||||
InstanceRenderManager({imex:
|
||||
<FormRow>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.federal_tax_rate")}
|
||||
name="federal_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.state_tax_rate")}
|
||||
name="state_tax_rate"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={1}
|
||||
precision={2}
|
||||
disabled={jobRO}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.local_tax_rate")}
|
||||
name="local_tax_rate"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
|
||||
<Form.Item
|
||||
label={t("jobs.fields.ca_gst_registrant")}
|
||||
name="ca_gst_registrant"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</FormRow>})
|
||||
}
|
||||
<Divider
|
||||
orientation="left"
|
||||
type="horizontal"
|
||||
@@ -233,8 +242,19 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
|
||||
<CurrencyInput min={0} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
{
|
||||
InstanceRenderManager({rome: <>
|
||||
<Divider orientation="left">Tax Profile</Divider>
|
||||
<JobsDetailRatesProfileOVerride form={form}/>
|
||||
<JobsDetailRatesParts form={form}/>
|
||||
</div>
|
||||
<JobsDetailRatesLabor form={form}/>
|
||||
<JobsDetailRatesMaterials form={form}/>
|
||||
<JobsDetailRatesOther form={form}/>
|
||||
<JobsDetailRatesTaxes form={form}/>
|
||||
|
||||
</>})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
import {Collapse, Form, Switch} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
|
||||
export function JobsDetailRatesLabor({
|
||||
jobRO,
|
||||
expanded,
|
||||
required = true,
|
||||
form,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||
<Collapse.Panel
|
||||
forceRender
|
||||
header={t("jobs.labels.cieca_pfl")}
|
||||
key="cieca_pfl"
|
||||
>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAB")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAB", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAD")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAD", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAE")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAE", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAF")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAF", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAG")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAG", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAM")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAM", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAR")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAR", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAS")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAS", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.lbr_types.LAU")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tax_in")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tax_in"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in1")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in2")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in3")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in4")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfl.lbr_tx_in5")}
|
||||
name={["cieca_pfl", "LAU", "lbr_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailRatesLabor);
|
||||
@@ -0,0 +1,147 @@
|
||||
import {Collapse, Form, Input, InputNumber, Switch} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
|
||||
export function JobsDetailRatesMaterials({
|
||||
jobRO,
|
||||
expanded,
|
||||
required = true,
|
||||
form,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||
<Collapse.Panel
|
||||
forceRender
|
||||
header={t("jobs.fields.materials.materials")}
|
||||
key="materials"
|
||||
>
|
||||
<LayoutFormRow header={t("jobs.fields.materials.MAPA")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.cal_maxdlr")}
|
||||
name={["materials", "MAPA", "cal_maxdlr"]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.cal_opcode")}
|
||||
name={["materials", "MAPA", "cal_opcode"]}
|
||||
>
|
||||
<Input disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.tax_ind")}
|
||||
name={["materials", "MAPA", "tax_ind"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in1")}
|
||||
name={["materials", "MAPA", "mat_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in2")}
|
||||
name={["materials", "MAPA", "mat_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in3")}
|
||||
name={["materials", "MAPA", "mat_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in4")}
|
||||
name={["materials", "MAPA", "mat_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in5")}
|
||||
name={["materials", "MAPA", "mat_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("jobs.fields.materials.MASH")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.cal_maxdlr")}
|
||||
name={["materials", "MASH", "cal_maxdlr"]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.cal_opcode")}
|
||||
name={["materials", "MASH", "cal_opcode"]}
|
||||
>
|
||||
<Input disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.tax_ind")}
|
||||
name={["materials", "MASH", "tax_ind"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in1")}
|
||||
name={["materials", "MASH", "mat_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in2")}
|
||||
name={["materials", "MASH", "mat_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in3")}
|
||||
name={["materials", "MASH", "mat_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in4")}
|
||||
name={["materials", "MASH", "mat_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.materials.mat_tx_in5")}
|
||||
name={["materials", "MASH", "mat_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailRatesMaterials);
|
||||
@@ -0,0 +1,106 @@
|
||||
import {Collapse, Form, Switch} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
});
|
||||
|
||||
export function JobsDetailRatesOther({
|
||||
jobRO,
|
||||
expanded,
|
||||
required = true,
|
||||
form,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||
<Collapse.Panel
|
||||
forceRender
|
||||
header={t("jobs.labels.cieca_pfo")}
|
||||
key="cieca_pfo"
|
||||
>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.tow_t_in1")}
|
||||
name={["cieca_pfo", "tow_t_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.tow_t_in2")}
|
||||
name={["cieca_pfo", "tow_t_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.tow_t_in3")}
|
||||
name={["cieca_pfo", "tow_t_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.tow_t_in4")}
|
||||
name={["cieca_pfo", "tow_t_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.tow_t_in5")}
|
||||
name={["cieca_pfo", "tow_t_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.stor_t_in1")}
|
||||
name={["cieca_pfo", "stor_t_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.stor_t_in2")}
|
||||
name={["cieca_pfo", "stor_t_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.stor_t_in3")}
|
||||
name={["cieca_pfo", "stor_t_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.stor_t_in4")}
|
||||
name={["cieca_pfo", "stor_t_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.cieca_pfo.stor_t_in5")}
|
||||
name={["cieca_pfo", "stor_t_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailRatesOther);
|
||||
@@ -30,7 +30,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAA", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -43,7 +43,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAA", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -69,18 +69,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAA", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAA", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAA", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAA", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAA", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAC")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAC", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -93,7 +133,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAC", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -119,18 +159,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAC", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAC", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAC", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAC", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAC", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAL")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAL", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -143,7 +223,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAL", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -169,18 +249,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAL", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAL", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAL", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAL", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAL", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAG")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAG", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -193,7 +313,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAG", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -219,18 +339,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAG", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAG", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAG", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAG", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAG", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAM")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAM", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -243,7 +403,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAM", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -269,18 +429,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAM", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAM", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAM", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAM", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAM", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAN")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAN", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -293,7 +493,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAN", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -319,18 +519,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAN", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAN", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAN", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAN", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAN", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAO")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAO", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -343,7 +583,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAO", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -369,18 +609,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAO", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAO", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAO", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAO", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAO", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAP")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAP", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -393,7 +673,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAP", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -419,18 +699,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAP", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAP", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAP", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAP", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAP", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAR")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAR", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -443,7 +763,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAR", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -469,18 +789,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAR", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAR", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAR", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAR", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAR", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PAS")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PAS", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -493,7 +853,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PAS", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -519,18 +879,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PAS", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PAS", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PAS", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PAS", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PAS", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.PASL")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "PASL", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -543,7 +943,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "PASL", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -569,18 +969,58 @@ export function JobsDetailRatesParts({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber
|
||||
min={0}
|
||||
max={100}
|
||||
precision={4}
|
||||
disabled={jobRO}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in1")}
|
||||
name={["parts_tax_rates", "PASL", "prt_tx_in1"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in2")}
|
||||
name={["parts_tax_rates", "PASL", "prt_tx_in2"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in3")}
|
||||
name={["parts_tax_rates", "PASL", "prt_tx_in3"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in4")}
|
||||
name={["parts_tax_rates", "PASL", "prt_tx_in4"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tx_in5")}
|
||||
name={["parts_tax_rates", "PASL", "prt_tx_in5"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.CCDR")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "CCDR", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -593,7 +1033,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "CCDR", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -606,7 +1046,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
|
||||
name={["parts_tax_rates", "CCDR", "prt_tax_rt"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.CCF")}>
|
||||
@@ -614,7 +1054,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "CCF", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -627,7 +1067,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "CCF", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -640,7 +1080,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
|
||||
name={["parts_tax_rates", "CCF", "prt_tax_rt"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.CCM")}>
|
||||
@@ -648,7 +1088,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "CCM", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -661,7 +1101,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "CCM", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -674,7 +1114,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
|
||||
name={["parts_tax_rates", "CCM", "prt_tax_rt"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.CCC")}>
|
||||
@@ -682,7 +1122,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "CCC", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -695,7 +1135,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "CCC", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -708,7 +1148,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
|
||||
name={["parts_tax_rates", "CCC", "prt_tax_rt"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("joblines.fields.part_types.CCD")}>
|
||||
@@ -716,7 +1156,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_discp")}
|
||||
name={["parts_tax_rates", "CCD", "prt_discp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mktyp")}
|
||||
@@ -729,7 +1169,7 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_mkupp")}
|
||||
name={["parts_tax_rates", "CCD", "prt_mkupp"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_in")}
|
||||
@@ -742,39 +1182,39 @@ export function JobsDetailRatesParts({
|
||||
label={t("jobs.fields.parts_tax_rates.prt_tax_rt")}
|
||||
name={["parts_tax_rates", "CCD", "prt_tax_rt"]}
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow>
|
||||
<Form.Item label={t("jobs.fields.tax_tow_rt")} name="tax_tow_rt">
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_str_rt")} name="tax_str_rt">
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.tax_paint_mat_rt")}
|
||||
name="tax_paint_mat_rt"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.tax_shop_mat_rt")}
|
||||
name="tax_shop_mat_rt"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_sub_rt")} name="tax_sub_rt">
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.tax_lbr_rt")} name="tax_lbr_rt">
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.tax_levies_rt")}
|
||||
name="tax_levies_rt"
|
||||
>
|
||||
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/>
|
||||
<InputNumber min={0} max={100} precision={4} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import {Button, Popconfirm} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JobsDetailRatesProfileOVerride);
|
||||
|
||||
export function JobsDetailRatesProfileOVerride({bodyshop, form}) {
|
||||
const {t} = useTranslation();
|
||||
return (
|
||||
<Popconfirm
|
||||
onConfirm={() => {
|
||||
form.setFieldsValue({
|
||||
cieca_pft: {
|
||||
...bodyshop.md_responsibility_centers.taxes.tax_ty1,
|
||||
...bodyshop.md_responsibility_centers.taxes.tax_ty2,
|
||||
...bodyshop.md_responsibility_centers.taxes.tax_ty3,
|
||||
...bodyshop.md_responsibility_centers.taxes.tax_ty4,
|
||||
...bodyshop.md_responsibility_centers.taxes.tax_ty5,
|
||||
},
|
||||
materials: bodyshop.md_responsibility_centers.cieca_pfm,
|
||||
cieca_pfl: bodyshop.md_responsibility_centers.cieca_pfl,
|
||||
parts_tax_rates: bodyshop.md_responsibility_centers.parts_tax_rates,
|
||||
});
|
||||
}}
|
||||
title={t("jobs.actions.taxprofileoverride_confirm")}
|
||||
>
|
||||
<Button type="link">{t("jobs.actions.taxprofileoverride")}</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import {Collapse, Divider, Form, Input, InputNumber, Space} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function JobsDetailRatesTaxes({
|
||||
jobRO,
|
||||
expanded,
|
||||
bodyshop,
|
||||
required = true,
|
||||
form,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const formItems = [];
|
||||
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
||||
const section = [];
|
||||
|
||||
section.push(
|
||||
TaxFormItems({
|
||||
typeNum: tyCounter,
|
||||
rootElements: true,
|
||||
bodyshop,
|
||||
jobRO,
|
||||
})
|
||||
);
|
||||
|
||||
for (let iterator = 1; iterator <= 5; iterator++) {
|
||||
section.push(
|
||||
TaxFormItems({
|
||||
typeNum: tyCounter,
|
||||
typeNumIterator: iterator,
|
||||
rootElements: false,
|
||||
jobRO,
|
||||
})
|
||||
);
|
||||
}
|
||||
formItems.push(<>
|
||||
<Space wrap>
|
||||
{section}
|
||||
</Space>
|
||||
<Divider/>
|
||||
</>)
|
||||
|
||||
}
|
||||
return (
|
||||
<Collapse defaultActiveKey={expanded && "rates"}>
|
||||
<Collapse.Panel
|
||||
forceRender
|
||||
header={t("jobs.labels.cieca_pft")}
|
||||
key="cieca_pft"
|
||||
>
|
||||
{formItems}
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailRatesTaxes);
|
||||
|
||||
function TaxFormItems({
|
||||
typeNum,
|
||||
typeNumIterator,
|
||||
rootElements,
|
||||
bodyshopjobRO,
|
||||
jobRO,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
if (rootElements)
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_tax_type", {
|
||||
typeNum,
|
||||
typeNumIterator,
|
||||
})}
|
||||
// rules={[
|
||||
// {
|
||||
// required: true,
|
||||
// //message: t("general.validation.required"),
|
||||
// },
|
||||
// ]}
|
||||
name={["cieca_pft", `tax_type${typeNum}`]}
|
||||
>
|
||||
<Input disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_tax_tier", {
|
||||
typeNum,
|
||||
typeNumIterator,
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["cieca_pft", `ty${typeNum}_tier${typeNumIterator}`]}
|
||||
>
|
||||
<InputNumber precision={0} min={0} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_tax_thres", {
|
||||
typeNum,
|
||||
typeNumIterator,
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["cieca_pft", `ty${typeNum}_thres${typeNumIterator}`]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_tax_rate", {
|
||||
typeNum,
|
||||
typeNumIterator,
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["cieca_pft", `ty${typeNum}_rate${typeNumIterator}`]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_tax_sur", {
|
||||
typeNum,
|
||||
typeNumIterator,
|
||||
})}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["cieca_pft", `ty${typeNum}_sur${typeNumIterator}`]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} disabled={jobRO}/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {Popconfirm, Button} from "antd";
|
||||
import {Button, Popconfirm} from "antd";
|
||||
import React from "react";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
InputNumber,
|
||||
notification,
|
||||
Popover,
|
||||
Select,
|
||||
} from "antd";
|
||||
import {Button, Card, Form, InputNumber, notification, Popover, Select,} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {UPDATE_JOB} from "../../graphql/jobs.queries";
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
import {Button, Card, Col, notification, Row, Space, Table, Typography,} from "antd";
|
||||
import {SyncOutlined} from "@ant-design/icons";
|
||||
import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, {useEffect, useMemo, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import "./labor-allocations-table.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
});
|
||||
|
||||
export function PayrollLaborAllocationsTable({
|
||||
jobId,
|
||||
joblines,
|
||||
timetickets,
|
||||
bodyshop,
|
||||
adjustments,
|
||||
technician,
|
||||
refetch,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const [totals, setTotals] = useState([]);
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {
|
||||
columnKey: "cost_center",
|
||||
field: "cost_center",
|
||||
order: "ascend",
|
||||
},
|
||||
filteredInfo: {},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
async function CalculateTotals() {
|
||||
const {data} = await axios.post("/payroll/calculatelabor", {
|
||||
jobid: jobId,
|
||||
});
|
||||
setTotals(data);
|
||||
}
|
||||
|
||||
if (!!joblines && !!timetickets && !!bodyshop) {
|
||||
CalculateTotals();
|
||||
}
|
||||
if (!jobId) setTotals([]);
|
||||
}, [joblines, timetickets, bodyshop, adjustments, jobId]);
|
||||
|
||||
const convertedLines = useMemo(
|
||||
() => joblines && joblines.filter((j) => j.convertedtolbr),
|
||||
[joblines]
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("timetickets.fields.employee"),
|
||||
dataIndex: "employeeid",
|
||||
key: "employeeid",
|
||||
render: (text, record) => {
|
||||
if (record.employeeid === undefined) {
|
||||
return (
|
||||
<span style={{color: "tomato", fontWeight: "bolder"}}>
|
||||
{t("timetickets.labels.unassigned")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
const emp = bodyshop.employees.find((e) => e.id === record.employeeid);
|
||||
return `${emp?.first_name} ${emp?.last_name}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.mod_lbr_ty"),
|
||||
dataIndex: "mod_lbr_ty",
|
||||
key: "mod_lbr_ty",
|
||||
render: (text, record) =>
|
||||
record.employeeid === undefined ? (
|
||||
<span style={{color: "tomato", fontWeight: "bolder"}}>
|
||||
{t("timetickets.labels.unassigned")}
|
||||
</span>
|
||||
) : (
|
||||
t(`joblines.fields.lbr_types.${record.mod_lbr_ty?.toUpperCase()}`)
|
||||
),
|
||||
},
|
||||
// {
|
||||
// title: t("timetickets.fields.rate"),
|
||||
// dataIndex: "rate",
|
||||
// key: "rate",
|
||||
// },
|
||||
{
|
||||
title: t("jobs.labels.hrs_total"),
|
||||
dataIndex: "expectedHours",
|
||||
key: "expectedHours",
|
||||
sorter: (a, b) => a.expectedHours - b.expectedHours,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "expectedHours" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => record.expectedHours.toFixed(5),
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.hrs_claimed"),
|
||||
dataIndex: "claimedHours",
|
||||
key: "claimedHours",
|
||||
sorter: (a, b) => a.claimedHours - b.claimedHours,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "claimedHours" && state.sortedInfo.order,
|
||||
render: (text, record) =>
|
||||
record.claimedHours && record.claimedHours.toFixed(5),
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.difference"),
|
||||
dataIndex: "difference",
|
||||
|
||||
key: "difference",
|
||||
sorter: (a, b) => a.difference - b.difference,
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "difference" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
const difference = _.round(
|
||||
record.expectedHours - record.claimedHours,
|
||||
5
|
||||
);
|
||||
|
||||
return (
|
||||
<strong
|
||||
style={{
|
||||
color: difference >= 0 ? "green" : "red",
|
||||
}}
|
||||
>
|
||||
{difference}
|
||||
</strong>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
const convertedTableCols = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
key: "line_desc",
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.op_code_desc"),
|
||||
dataIndex: "op_code_desc",
|
||||
key: "op_code_desc",
|
||||
ellipsis: true,
|
||||
render: (text, record) =>
|
||||
`${record.op_code_desc || ""}${
|
||||
record.alt_partm ? ` ${record.alt_partm}` : ""
|
||||
}`,
|
||||
},
|
||||
|
||||
{
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
key: "act_price",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<>
|
||||
<CurrencyFormatter>
|
||||
{record.db_ref === "900510" || record.db_ref === "900511"
|
||||
? record.prt_dsmk_m
|
||||
: record.act_price}
|
||||
</CurrencyFormatter>
|
||||
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
||||
<span
|
||||
style={{marginLeft: ".2rem"}}
|
||||
>{`(${record.prt_dsmk_p}%)`}</span>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty",
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.mod_lbr_ty"),
|
||||
dataIndex: "conv_mod_lbr_ty",
|
||||
key: "conv_mod_lbr_ty",
|
||||
render: (text, record) =>
|
||||
record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty,
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.mod_lb_hrs"),
|
||||
dataIndex: "conv_mod_lb_hrs",
|
||||
key: "conv_mod_lb_hrs",
|
||||
render: (text, record) =>
|
||||
record.convertedtolbr_data &&
|
||||
record.convertedtolbr_data.mod_lb_hrs &&
|
||||
record.convertedtolbr_data.mod_lb_hrs.toFixed(5),
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
};
|
||||
|
||||
const summary =
|
||||
totals &&
|
||||
totals.reduce(
|
||||
(acc, val) => {
|
||||
acc.hrs_total += val.expectedHours;
|
||||
acc.hrs_claimed += val.claimedHours;
|
||||
// acc.adjustments += val.adjustments;
|
||||
acc.difference += val.expectedHours - val.claimedHours;
|
||||
return acc;
|
||||
},
|
||||
{hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0}
|
||||
);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Card
|
||||
title={t("jobs.labels.laborallocations")}
|
||||
extra={
|
||||
<Space>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const response = await axios.post("/payroll/payall", {
|
||||
jobid: jobId,
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
if (response.data.success !== false) {
|
||||
notification.open({
|
||||
type: "success",
|
||||
message: t("timetickets.successes.payall"),
|
||||
});
|
||||
} else {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.payall", {
|
||||
error: response.data.error,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (refetch) refetch();
|
||||
} else {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("timetickets.errors.payall", {
|
||||
error: JSON.stringify(""),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("timetickets.actions.payall")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const {data} = await axios.post("/payroll/calculatelabor", {
|
||||
jobid: jobId,
|
||||
});
|
||||
setTotals(data);
|
||||
refetch();
|
||||
}}
|
||||
>
|
||||
<SyncOutlined/>
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
rowKey={(record) => `${record.employeeid} ${record.mod_lbr_ty}`}
|
||||
pagination={false}
|
||||
onChange={handleTableChange}
|
||||
dataSource={totals}
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
summary={() => (
|
||||
<Table.Summary.Row>
|
||||
<Table.Summary.Cell>
|
||||
<Typography.Title level={4}>
|
||||
{t("general.labels.totals")}
|
||||
</Typography.Title>
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell></Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{summary.hrs_total.toFixed(5)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{summary.hrs_claimed.toFixed(5)}
|
||||
</Table.Summary.Cell>
|
||||
|
||||
<Table.Summary.Cell>
|
||||
{summary.difference.toFixed(5)}
|
||||
</Table.Summary.Cell>
|
||||
</Table.Summary.Row>
|
||||
)}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
{convertedLines && convertedLines.length > 0 && (
|
||||
<Col span={24}>
|
||||
<Card title={t("jobs.labels.convertedtolabor")}>
|
||||
<Table
|
||||
columns={convertedTableCols}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
dataSource={convertedLines}
|
||||
scroll={{
|
||||
x: true,
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(PayrollLaborAllocationsTable);
|
||||
@@ -0,0 +1,81 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Card, Col, notification, Row, Table} from "antd";
|
||||
import day from "../../utils/day";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {UPDATE_PARTS_DISPATCH_LINE} from "../../graphql/parts-dispatch.queries";
|
||||
import {DateTimeFormatter} from "../../utils/DateFormatter";
|
||||
|
||||
export default function PartsDispatchExpander({dispatch, job}) {
|
||||
const {t} = useTranslation();
|
||||
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
|
||||
|
||||
const handleAccept = async ({partsDispatchLineId}) => {
|
||||
const accepted_at = day();
|
||||
const result = await updateDispatchLine({
|
||||
variables: {id: partsDispatchLineId, line: {accepted_at}},
|
||||
optimisticResponse: {
|
||||
update_parts_dispatch_lines_by_pk: {
|
||||
accepted_at,
|
||||
id: partsDispatchLineId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (result.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("parts_dispatch.errors.accepting", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "quantity",
|
||||
key: "quantity",
|
||||
width: "10%",
|
||||
//sorter: (a, b) => alphaSort(a.number, b.number),
|
||||
},
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "joblineid",
|
||||
key: "joblineid",
|
||||
//sorter: (a, b) => alphaSort(a.number, b.number),
|
||||
render: (text, record) => record.jobline.line_desc,
|
||||
},
|
||||
{
|
||||
title: t("parts_dispatch_lines.fields.accepted_at"),
|
||||
dataIndex: "accepted_at",
|
||||
key: "accepted_at",
|
||||
width: "20%",
|
||||
|
||||
//sorter: (a, b) => alphaSort(a.number, b.number),
|
||||
render: (text, record) =>
|
||||
record.accepted_at ? (
|
||||
<DateTimeFormatter>{record.accepted_at}</DateTimeFormatter>
|
||||
) : (
|
||||
<Button
|
||||
onClick={() => handleAccept({partsDispatchLineId: record.id})}
|
||||
>
|
||||
{t("parts_dispatch.actions.accept")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Card>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Table
|
||||
rowKey={"id"}
|
||||
dataSource={dispatch.parts_dispatch_lines}
|
||||
columns={columns}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import {MinusCircleTwoTone, PlusCircleTwoTone, SyncOutlined,} from "@ant-design/icons";
|
||||
import {Button, Card, Input, Space, Table} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectJobReadOnly} from "../../redux/application/application.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
import {alphaSort} from "../../utils/sorters";
|
||||
import PartsDispatchExpander from "../parts-dispatch-expander/parts-dispatch-expander.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function PartDispatchTableComponent({
|
||||
bodyshop,
|
||||
jobRO,
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
});
|
||||
// const search = queryString.parse(useLocation().search);
|
||||
// const selectedBill = search.billid;
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const Templates = TemplateList("job_special");
|
||||
|
||||
const {refetch} = billsQuery;
|
||||
|
||||
const recordActions = (record) => (
|
||||
<Space wrap>
|
||||
<PrintWrapperComponent
|
||||
templateObject={{
|
||||
name: Templates.parts_dispatch.key,
|
||||
variables: {id: record.id},
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
const columns = [
|
||||
{
|
||||
title: t("parts_dispatch.fields.number"),
|
||||
dataIndex: "number",
|
||||
key: "number",
|
||||
sorter: (a, b) => alphaSort(a.number, b.number),
|
||||
width: "10%",
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "number" && state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.employee"),
|
||||
dataIndex: "employeeid",
|
||||
key: "employeeid",
|
||||
sorter: (a, b) => alphaSort(a.employeeid, b.employeeid),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "employeeid" && state.sortedInfo.order,
|
||||
render: (text, record) => {
|
||||
const e = bodyshop.employees.find((e) => e.id === record.employeeid);
|
||||
return `${e?.first_name || ""} ${e?.last_name || ""}`.trim();
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("parts_dispatch.fields.percent_accepted"),
|
||||
dataIndex: "percent_accepted",
|
||||
key: "percent_accepted",
|
||||
|
||||
render: (text, record) =>
|
||||
record.parts_dispatch_lines.length > 0
|
||||
? `
|
||||
${(
|
||||
(record.parts_dispatch_lines.filter((l) => l.accepted_at)
|
||||
.length /
|
||||
record.parts_dispatch_lines.length) *
|
||||
100
|
||||
).toFixed(0)}%`
|
||||
: "0%",
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
width: "10%",
|
||||
render: (text, record) => recordActions(record, true),
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t("parts_dispatch.labels.parts_dispatch")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined/>
|
||||
</Button>
|
||||
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
value={searchText}
|
||||
onChange={(e) => {
|
||||
e.preventDefault();
|
||||
setSearchText(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Table
|
||||
loading={billsQuery.loading}
|
||||
scroll={{
|
||||
x: true, // y: "50rem"
|
||||
}}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => (
|
||||
<PartsDispatchExpander dispatch={record} job={job}/>
|
||||
),
|
||||
rowExpandable: (record) => true,
|
||||
|
||||
expandIcon: ({expanded, onExpand, record}) =>
|
||||
expanded ? (
|
||||
<MinusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
|
||||
) : (
|
||||
<PlusCircleTwoTone onClick={(e) => onExpand(record, e)}/>
|
||||
),
|
||||
}}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={billsQuery.data ? billsQuery.data.parts_dispatch : []}
|
||||
onChange={handleTableChange}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(PartDispatchTableComponent);
|
||||
@@ -5,10 +5,7 @@ import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -37,16 +34,16 @@ export function BillMarkSelectedExported({
|
||||
const [open, setOpen] = useState(false);
|
||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||
const [updateBill] = useMutation(gql`
|
||||
mutation UPDATE_BILL($billIds: [uuid!]!) {
|
||||
update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) {
|
||||
returning {
|
||||
id
|
||||
exported
|
||||
exported_at
|
||||
mutation UPDATE_BILL($billIds: [uuid!]!) {
|
||||
update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) {
|
||||
returning {
|
||||
id
|
||||
exported
|
||||
exported_at
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const handleUpdate = async () => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import {useMutation, useQuery} from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Descriptions,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Space,
|
||||
notification,
|
||||
} from "antd";
|
||||
import {Button, Descriptions, InputNumber, Modal, notification, Space,} from "antd";
|
||||
import axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, {useState} from "react";
|
||||
|
||||
@@ -5,10 +5,7 @@ import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {INSERT_EXPORT_LOG} from "../../graphql/accounting.queries";
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
} from "../../redux/user/user.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -38,18 +35,18 @@ export function PaymentMarkSelectedExported({
|
||||
|
||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||
const [updatePayments] = useMutation(gql`
|
||||
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
|
||||
update_payments(
|
||||
where: { id: { _in: $paymentIds } }
|
||||
_set: { exportedat: $exportedat }
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
exportedat
|
||||
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
|
||||
update_payments(
|
||||
where: { id: { _in: $paymentIds } }
|
||||
_set: { exportedat: $exportedat }
|
||||
) {
|
||||
returning {
|
||||
id
|
||||
exportedat
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
`);
|
||||
|
||||
const handleUpdate = async () => {
|
||||
setLoading(true);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Card, Col, Input, Row, Space, Typography} from "antd";
|
||||
import _ from "lodash";
|
||||
import React, {useState} from "react";
|
||||
@@ -24,8 +25,14 @@ export function PrintCenterJobsComponent({printCenterModal, bodyshop}) {
|
||||
const {id: jobId, job} = printCenterModal.context;
|
||||
const tempList = TemplateList("job", {});
|
||||
const {t} = useTranslation();
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const JobsReportsList =
|
||||
|
||||
const Templates =
|
||||
bodyshop.cdk_dealerid === null && bodyshop.pbs_serialnumber === null
|
||||
? Object.keys(tempList)
|
||||
.map((key) => {
|
||||
@@ -52,7 +59,26 @@ export function PrintCenterJobsComponent({printCenterModal, bodyshop}) {
|
||||
bodyshop.region_config.includes(Object.keys(temp.regions)) ===
|
||||
true)
|
||||
);
|
||||
|
||||
const JobsReportsList =
|
||||
Enhanced_Payroll.treatment === "on"
|
||||
? Object.keys(Templates)
|
||||
.map((key) => {
|
||||
return Templates[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
temp.enhanced_payroll === undefined ||
|
||||
temp.enhanced_payroll === true
|
||||
)
|
||||
: Object.keys(Templates)
|
||||
.map((key) => {
|
||||
return Templates[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
temp.enhanced_payroll === undefined ||
|
||||
temp.enhanced_payroll === false
|
||||
);
|
||||
const filteredJobsReportsList =
|
||||
search !== ""
|
||||
? JobsReportsList.filter((r) =>
|
||||
|
||||
@@ -9,9 +9,11 @@ export default function PrintWrapperComponent({
|
||||
children,
|
||||
id,
|
||||
emailOnly = false,
|
||||
disabled,
|
||||
}) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handlePrint = async (type) => {
|
||||
if (disabled) return;
|
||||
setLoading(true);
|
||||
await GenerateDocument(templateObject, messageObject, type, id);
|
||||
setLoading(false);
|
||||
@@ -20,8 +22,18 @@ export default function PrintWrapperComponent({
|
||||
return (
|
||||
<Space>
|
||||
{children || null}
|
||||
{!emailOnly && <PrinterFilled onClick={() => handlePrint("p")}/>}
|
||||
<MailFilled onClick={() => handlePrint("e")}/>
|
||||
{!emailOnly && (
|
||||
<PrinterFilled
|
||||
disabled={disabled}
|
||||
onClick={() => handlePrint("p")}
|
||||
style={{cursor: disabled ? "not-allowed" : null}}
|
||||
/>
|
||||
)}
|
||||
<MailFilled
|
||||
disabled={disabled}
|
||||
onClick={() => handlePrint("e")}
|
||||
style={{cursor: disabled ? "not-allowed" : null}}
|
||||
/>
|
||||
{loading && <Spin/>}
|
||||
</Space>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
notification,
|
||||
Popover,
|
||||
Row,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import {Button, Card, Col, Form, notification, Popover, Row, Switch,} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {UPDATE_KANBAN_SETTINGS} from "../../graphql/user.queries";
|
||||
|
||||
@@ -25,6 +25,8 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
|
||||
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||
import {store} from "../../redux/store";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
|
||||
const r = ({technician, state, activeStatuses, data, bodyshop}) => {
|
||||
return [
|
||||
@@ -39,6 +41,29 @@ const r = ({technician, state, activeStatuses, data, bodyshop}) => {
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("timetickets.actions.claimtasks"),
|
||||
dataIndex: "claimtasks",
|
||||
key: "claimtasks",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
store.dispatch(
|
||||
setModalContext({
|
||||
context: {
|
||||
actions: {},
|
||||
context: {jobid: record.id},
|
||||
},
|
||||
modal: "timeTicketTask",
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
{i18n.t("timetickets.actions.claimtasks")}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.ro_number"),
|
||||
dataIndex: "ro_number",
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
import {DeleteFilled, PlusCircleFilled} from "@ant-design/icons";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
notification,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Spin,
|
||||
} from "antd";
|
||||
import {Button, Col, notification, Popover, Row, Select, Space, Spin,} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Card, Dropdown, Form, Input, notification, Space} from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, {useState, useEffect} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
|
||||
@@ -98,7 +98,7 @@ export function ProductionListDetail({
|
||||
/>
|
||||
}
|
||||
placement="right"
|
||||
width={"33%"}
|
||||
width={"50%"}
|
||||
onClose={handleClose}
|
||||
open={selected}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {useLazyQuery} from "@apollo/client";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {Button, Card, Col, DatePicker, Form, Input, Radio, Row, Typography,} from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
@@ -9,6 +10,7 @@ import {createStructuredSelector} from "reselect";
|
||||
import {QUERY_ACTIVE_EMPLOYEES} from "../../graphql/employees.queries";
|
||||
import {QUERY_ALL_VENDORS} from "../../graphql/vendors.queries";
|
||||
import {selectReportCenter} from "../../redux/modals/modals.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
||||
import {GenerateDocument} from "../../utils/RenderTemplate";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
@@ -18,6 +20,7 @@ import "./report-center-modal.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
reportCenterModal: selectReportCenter,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -27,16 +30,39 @@ export default connect(
|
||||
mapDispatchToProps
|
||||
)(ReportCenterModalComponent);
|
||||
|
||||
export function ReportCenterModalComponent({reportCenterModal}) {
|
||||
export function ReportCenterModalComponent({reportCenterModal, bodyshop}) {
|
||||
const [form] = Form.useForm();
|
||||
const [search, setSearch] = useState("");
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const {t} = useTranslation();
|
||||
const Templates = TemplateList("report_center");
|
||||
const ReportsList = Object.keys(Templates).map((key) => {
|
||||
return Templates[key];
|
||||
});
|
||||
const ReportsList =
|
||||
Enhanced_Payroll.treatment === "on"
|
||||
? Object.keys(Templates)
|
||||
.map((key) => {
|
||||
return Templates[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
temp.enhanced_payroll === undefined ||
|
||||
temp.enhanced_payroll === true
|
||||
)
|
||||
: Object.keys(Templates)
|
||||
.map((key) => {
|
||||
return Templates[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
temp.enhanced_payroll === undefined ||
|
||||
temp.enhanced_payroll === false
|
||||
);
|
||||
|
||||
const {open} = reportCenterModal;
|
||||
|
||||
const [callVendorQuery, {data: vendorData, called: vendorCalled}] =
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
Card,
|
||||
Col,
|
||||
Form,
|
||||
Row,
|
||||
Space,
|
||||
Statistic,
|
||||
Switch,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import {Card, Col, Form, Row, Space, Statistic, Switch, Typography,} from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
|
||||
@@ -26,6 +26,7 @@ import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.c
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
||||
import queryString from "query-string";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -47,6 +48,12 @@ export function ShopEmployeesFormComponent({bodyshop}) {
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const client = useApolloClient();
|
||||
useEffect(() => {
|
||||
if (data && data.employees_by_pk) form.setFieldsValue(data.employees_by_pk);
|
||||
@@ -352,7 +359,7 @@ export function ShopEmployeesFormComponent({bodyshop}) {
|
||||
{t("timetickets.labels.shift")}
|
||||
</Select.Option>
|
||||
|
||||
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
||||
? CiecaSelect(false, true)
|
||||
: bodyshop.md_responsibility_centers.costs.map(
|
||||
(c) => (
|
||||
|
||||
@@ -15,7 +15,6 @@ export default function ShopEmployeesListComponent({loading, employees}) {
|
||||
filteredInfo: {text: ""},
|
||||
});
|
||||
|
||||
|
||||
const handleOnRowClick = (record) => {
|
||||
if (record) {
|
||||
search.employeeId = record.id;
|
||||
@@ -25,11 +24,9 @@ export default function ShopEmployeesListComponent({loading, employees}) {
|
||||
history({search: queryString.stringify(search)});
|
||||
}
|
||||
};
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
setState({...state, filteredInfo: filters, sortedInfo: sorter});
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("employees.fields.employee_number"),
|
||||
|
||||
@@ -16,6 +16,7 @@ import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
|
||||
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import ShopInfoTaskPresets from "./shop-info.task-presets.component";
|
||||
import queryString from "query-string";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -28,7 +29,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
|
||||
|
||||
export function ShopInfoComponent({bodyshop, form, saveLoading}) {
|
||||
|
||||
const {treatments: {CriticalPartsScanning}} = useSplitTreatments({
|
||||
const {treatments: {CriticalPartsScanning, EnhancedPayroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["CriticalPartsScanning"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
@@ -94,6 +95,12 @@ export function ShopInfoComponent({bodyshop, form, saveLoading}) {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...EnhancedPayroll.treatment === "on" ? [
|
||||
{
|
||||
key: 'task-presets',
|
||||
label: t("bodyshop.labels.task-presets"),
|
||||
children: <ShopInfoTaskPresets form={form}/>
|
||||
}]: []
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
@@ -12,6 +12,7 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
import PhoneFormItem, {PhoneItemFormatterValidation,} from "../form-items-formatted/phone-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import InstanceRenderManager from '../../utils/instanceRenderMgr'
|
||||
// TODO: Client Update, this might break
|
||||
const timeZonesList = Intl.supportedValuesOf('timeZone');
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -177,9 +178,10 @@ export function ShopInfoGeneral({form, bodyshop}) {
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => (
|
||||
{
|
||||
InstanceRenderManager({imex:
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => (
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo_usa")}
|
||||
shouldUpdate
|
||||
@@ -187,11 +189,13 @@ export function ShopInfoGeneral({form, bodyshop}) {
|
||||
name={["accountingconfig", "qbo_usa"]}
|
||||
>
|
||||
<Switch
|
||||
disabled={!form.getFieldValue(["accountingconfig", "qbo"])}
|
||||
disabled={!form.getFieldValue(["accountingconfig", "qbo"])}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
})
|
||||
}
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo_departmentid")}
|
||||
name={["accountingconfig", "qbo_departmentid"]}
|
||||
@@ -280,36 +284,37 @@ export function ShopInfoGeneral({form, bodyshop}) {
|
||||
>
|
||||
<InputNumber min={0} precision={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.federal_tax_id")}
|
||||
name="federal_tax_id"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
{
|
||||
InstanceRenderManager({imex:
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.federal_tax_id")}
|
||||
name="federal_tax_id"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item> })
|
||||
}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.state_tax_id")}
|
||||
name="state_tax_id"
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_federal_tax_rate")}
|
||||
name={["bill_tax_rates", "federal_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
{
|
||||
InstanceRenderManager({imex:
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_federal_tax_rate")}
|
||||
name={["bill_tax_rates", "federal_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
})
|
||||
}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_state_tax_rate")}
|
||||
name={["bill_tax_rates", "state_tax_rate"]}
|
||||
|
||||
@@ -26,7 +26,7 @@ export function ShopInfoRbacComponent({form, bodyshop}) {
|
||||
names: ["Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
//TODO:AIO Ensure that there are no duplicates here, it seems like there may be.
|
||||
return (
|
||||
<RbacWrapper action="shop:rbac">
|
||||
<LayoutFormRow>
|
||||
@@ -403,29 +403,66 @@ export function ShopInfoRbacComponent({form, bodyshop}) {
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.owners.detail")}
|
||||
label={t("bodyshop.fields.rbac.owners.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "owners:detail"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.owners.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "owners:list"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "owners:detail"]}
|
||||
name={["md_rbac", "bills:list"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.owners.list")}
|
||||
label={t("bodyshop.fields.rbac.employees.page")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "owners:list"]}
|
||||
name={["md_rbac", "employees:page"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.employee_teams.page")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "employee_teams:page"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.payments.enter")}
|
||||
rules={[
|
||||
@@ -535,14 +572,86 @@ export function ShopInfoRbacComponent({form, bodyshop}) {
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.config")}
|
||||
label={t("bodyshop.fields.rbac.shop.config")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "shop:config"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "shop:config"]}
|
||||
name={["md_rbac", "timetickets:edit"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "timetickets:shiftedit"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "timetickets:editcommitted"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:view"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:approve"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_rbac", "shop:vendors"]}
|
||||
>
|
||||
<InputNumber/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -9,6 +9,8 @@ import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import ShopInfoResponsibilitycentersTaxesComponent from "./shop-info.responsibilitycenters.taxes.component";
|
||||
import InstanceRenderManager from '../../utils/instanceRenderMgr';
|
||||
|
||||
const SelectorDiv = styled.div`
|
||||
.ant-form-item .ant-select {
|
||||
@@ -4336,7 +4338,10 @@ export function ShopInfoResponsibilityCenterComponent({bodyshop, form}) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
<LayoutFormRow id="state_tax">
|
||||
|
||||
|
||||
{
|
||||
InstanceRenderManager({imex: <LayoutFormRow id="state_tax">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
|
||||
rules={[
|
||||
@@ -4433,105 +4438,132 @@ export function ShopInfoResponsibilityCenterComponent({bodyshop, form}) {
|
||||
>
|
||||
<InputNumber precision={2}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow id="local_tax">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.local_tax")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "local", "name"]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={[
|
||||
"md_responsibility_centers",
|
||||
"taxes",
|
||||
"local",
|
||||
"accountnumber",
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountname")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "local", "accountname"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "local", "accountdesc"]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "local", "accountitem"]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dms.dms_acctnumber")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={[
|
||||
"md_responsibility_centers",
|
||||
"taxes",
|
||||
"local",
|
||||
"dms_acctnumber",
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_rate")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "local", "rate"]}
|
||||
>
|
||||
<InputNumber precision={2}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</LayoutFormRow>,
|
||||
rome: <ShopInfoResponsibilitycentersTaxesComponent form={form}/> })
|
||||
}
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.itemexemptcode")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "itemexemptcode"]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.invoiceexemptcode")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "taxes", "invoiceexemptcode"]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
{/*<LayoutFormRow id="local_tax">*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.responsibilitycenters.local_tax")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={["md_responsibility_centers", "taxes", "local", "name"]}*/}
|
||||
{/* >*/}
|
||||
{/* <Input />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/* /!* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.responsibilitycenter_accountnumber")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={[*/}
|
||||
{/* "md_responsibility_centers",*/}
|
||||
{/* "taxes",*/}
|
||||
{/* "local",*/}
|
||||
{/* "accountnumber",*/}
|
||||
{/* ]}*/}
|
||||
{/* >*/}
|
||||
{/* <Input />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.responsibilitycenter_accountname")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={["md_responsibility_centers", "taxes", "local", "accountname"]}*/}
|
||||
{/* >*/}
|
||||
{/* <Input />*/}
|
||||
{/* </Form.Item> *!/*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.responsibilitycenter_accountdesc")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={["md_responsibility_centers", "taxes", "local", "accountdesc"]}*/}
|
||||
{/* >*/}
|
||||
{/* <Input />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.responsibilitycenter_accountitem")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={["md_responsibility_centers", "taxes", "local", "accountitem"]}*/}
|
||||
{/* >*/}
|
||||
{/* <Input />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/* {(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.dms.dms_acctnumber")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={[*/}
|
||||
{/* "md_responsibility_centers",*/}
|
||||
{/* "taxes",*/}
|
||||
{/* "local",*/}
|
||||
{/* "dms_acctnumber",*/}
|
||||
{/* ]}*/}
|
||||
{/* >*/}
|
||||
{/* <Input />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/* )}*/}
|
||||
{/* <Form.Item*/}
|
||||
{/* label={t("bodyshop.fields.responsibilitycenter_rate")}*/}
|
||||
{/* rules={[*/}
|
||||
{/* {*/}
|
||||
{/* required: true,*/}
|
||||
{/* //message: t("general.validation.required"),*/}
|
||||
{/* },*/}
|
||||
{/* ]}*/}
|
||||
{/* name={["md_responsibility_centers", "taxes", "local", "rate"]}*/}
|
||||
{/* >*/}
|
||||
{/* <InputNumber precision={2} />*/}
|
||||
{/* </Form.Item>*/}
|
||||
{/*</LayoutFormRow>*/}
|
||||
<LayoutFormRow header={<div>AR</div>} id="AR">
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.ar")}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,259 @@
|
||||
import {DeleteFilled} from "@ant-design/icons";
|
||||
import {Button, Checkbox, Col, Form, Input, InputNumber, Row, Select, Space, Switch,} from "antd";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ShopInfoTaskPresets);
|
||||
|
||||
export function ShopInfoTaskPresets({bodyshop, form}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.enable_tasks")}
|
||||
valuePropName="checked"
|
||||
name={["md_tasks_presets", "enable_tasks"]}
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.use_approvals")}
|
||||
valuePropName="checked"
|
||||
name={["md_tasks_presets", "use_approvals"]}
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow header={t("bodyshop.labels.md_tasks_presets")}>
|
||||
<Form.List name={["md_tasks_presets", "presets"]}>
|
||||
{(fields, {add, remove, move}) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
span={12}
|
||||
label={t("bodyshop.fields.md_tasks_presets.hourstype")}
|
||||
key={`${index}hourstype`}
|
||||
name={[field.name, "hourstype"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Checkbox.Group>
|
||||
<Row>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAA"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAA")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAB"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAB")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAD"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAD")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAE"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAE")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAF"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAF")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAG"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAG")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAM"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAM")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAR"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAR")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAS"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAS")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LAU"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LAU")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LA1"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LA1")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LA2"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LA2")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LA3"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LA3")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Checkbox
|
||||
value="LA4"
|
||||
style={{lineHeight: "32px"}}
|
||||
>
|
||||
{t("joblines.fields.lbr_types.LA4")}
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.percent")}
|
||||
key={`${index}percent`}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={[field.name, "percent"]}
|
||||
>
|
||||
<InputNumber min={0} max={100}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.memo")}
|
||||
key={`${index}memo`}
|
||||
name={[field.name, "memo"]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_tasks_presets.nextstatus")}
|
||||
key={`${index}nextstatus`}
|
||||
name={[field.name, "nextstatus"]}
|
||||
>
|
||||
<Select
|
||||
options={bodyshop.md_ro_statuses.production_statuses.map(
|
||||
(o) => ({value: o, label: o})
|
||||
)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Space wrap>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows
|
||||
move={move}
|
||||
index={index}
|
||||
total={fields.length}
|
||||
/>
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
{t("bodyshop.actions.add_task_preset")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function ShopEmployeeTeamMember({teamMember}) {
|
||||
return (
|
||||
<div>ShopEmployeeTeamMember</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
import {DeleteFilled} from "@ant-design/icons";
|
||||
import {useMutation, useQuery} from "@apollo/client";
|
||||
import {Button, Card, Form, Input, InputNumber, notification, Space, Switch,} from "antd";
|
||||
|
||||
import querystring from "query-string";
|
||||
import React, {useEffect} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {logImEXEvent} from "../../firebase/firebase.utils";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
import {
|
||||
INSERT_EMPLOYEE_TEAM,
|
||||
QUERY_EMPLOYEE_TEAM_BY_ID,
|
||||
UPDATE_EMPLOYEE_TEAM,
|
||||
} from "../../graphql/employee_teams.queries";
|
||||
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export function ShopEmployeeTeamsFormComponent({bodyshop}) {
|
||||
const {t} = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const history = useNavigate();
|
||||
const search = querystring.parse(useLocation().search);
|
||||
|
||||
const {error, data} = useQuery(QUERY_EMPLOYEE_TEAM_BY_ID, {
|
||||
variables: {id: search.employeeTeamId},
|
||||
skip: !search.employeeTeamId || search.employeeTeamId === "new",
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data && data.employee_teams_by_pk)
|
||||
form.setFieldsValue(data.employee_teams_by_pk);
|
||||
else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [form, data, search.employeeTeamId]);
|
||||
|
||||
const [updateEmployeeTeam] = useMutation(UPDATE_EMPLOYEE_TEAM);
|
||||
const [insertEmployeeTeam] = useMutation(INSERT_EMPLOYEE_TEAM);
|
||||
|
||||
const handleFinish = async ({employee_team_members, ...values}) => {
|
||||
if (search.employeeTeamId && search.employeeTeamId !== "new") {
|
||||
//Update a record.
|
||||
logImEXEvent("shop_employee_update");
|
||||
|
||||
const result = await updateEmployeeTeam({
|
||||
variables: {
|
||||
employeeTeamId: search.employeeTeamId,
|
||||
employeeTeam: values,
|
||||
teamMemberUpdates: employee_team_members
|
||||
.filter((e) => e.id)
|
||||
.map((e) => {
|
||||
delete e.__typename;
|
||||
return {where: {id: {_eq: e.id}}, _set: e};
|
||||
}),
|
||||
teamMemberInserts: employee_team_members
|
||||
.filter((e) => e.id === null || e.id === undefined)
|
||||
.map((e) => ({...e, teamid: search.employeeTeamId})),
|
||||
teamMemberDeletes:
|
||||
data.employee_teams_by_pk.employee_team_members.filter(
|
||||
(e) => !employee_team_members.find((etm) => etm.id === e.id)
|
||||
),
|
||||
},
|
||||
});
|
||||
if (!result.errors) {
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save"),
|
||||
});
|
||||
} else {
|
||||
notification["error"]({
|
||||
message: t("employees.errors.save", {
|
||||
message: JSON.stringify(error),
|
||||
}),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
//New record, insert it.
|
||||
logImEXEvent("shop_employee_insert");
|
||||
|
||||
insertEmployeeTeam({
|
||||
variables: {
|
||||
employeeTeam: {
|
||||
...values,
|
||||
employee_team_members: {data: employee_team_members},
|
||||
bodyshopid: bodyshop.id,
|
||||
},
|
||||
},
|
||||
refetchQueries: ["QUERY_TEAMS"],
|
||||
}).then((r) => {
|
||||
search.employeeTeamId = r.data.insert_employee_teams_one.id;
|
||||
history({search: querystring.stringify(search)});
|
||||
notification["success"]({
|
||||
message: t("employees.successes.save"),
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (!search.employeeTeamId) return null;
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
|
||||
return (
|
||||
<Card
|
||||
extra={
|
||||
<Button type="primary" onClick={() => form.submit()}>
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
autoComplete={"off"}
|
||||
layout="vertical"
|
||||
form={form}
|
||||
>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label={t("employee_teams.fields.name")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("employee_teams.fields.active")}
|
||||
name="active"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("employee_teams.fields.max_load")}
|
||||
name="max_load"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={1}/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Form.List name={["employee_team_members"]}>
|
||||
{(fields, {add, remove, move}) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key} style={{padding: 0, margin: 2}}>
|
||||
<Form.Item
|
||||
label={t("employees.fields.id")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "id"]}
|
||||
hidden
|
||||
>
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
label={t("employee_teams.fields.employeeid")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "employeeid"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<EmployeeSearchSelectComponent
|
||||
options={bodyshop.employees}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("employee_teams.fields.percentage")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "percentage"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={2}/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAA")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAA"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAB")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAB"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAD")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAD"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAE")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAE"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAF")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAF"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAG")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAG"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAM")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAM"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAR")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAR"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAS")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAS"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LAU")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LAU"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LA1")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LA1"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LA2")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LA2"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LA3")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LA3"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.lbr_types.LA4")}
|
||||
key={`${index}`}
|
||||
name={[field.name, "labor_rates", "LA4"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput/>
|
||||
</Form.Item>
|
||||
<Space align="center">
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows
|
||||
move={move}
|
||||
index={index}
|
||||
total={fields.length}
|
||||
/>
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{width: "100%"}}
|
||||
>
|
||||
{t("employee_teams.actions.newmember")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ShopEmployeeTeamsFormComponent);
|
||||
@@ -0,0 +1,71 @@
|
||||
import {Button, Table} from "antd";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
|
||||
export default function ShopEmployeeTeamsListComponent({
|
||||
loading,
|
||||
employee_teams,
|
||||
}) {
|
||||
const {t} = useTranslation();
|
||||
const history = useNavigate();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
const handleOnRowClick = (record) => {
|
||||
if (record) {
|
||||
search.employeeTeamId = record.id;
|
||||
history({search: queryString.stringify(search)});
|
||||
} else {
|
||||
delete search.employeeTeamId;
|
||||
history({search: queryString.stringify(search)});
|
||||
}
|
||||
};
|
||||
const columns = [
|
||||
{
|
||||
title: t("employee_teams.fields.name"),
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
title={() => {
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
search.employeeTeamId = "new";
|
||||
history({search: queryString.stringify(search)});
|
||||
}}
|
||||
>
|
||||
{t("employee_teams.actions.new")}
|
||||
</Button>
|
||||
);
|
||||
}}
|
||||
loading={loading}
|
||||
pagination={{position: "top"}}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
dataSource={employee_teams}
|
||||
rowSelection={{
|
||||
onSelect: (props) => {
|
||||
search.employeeTeamId = props.id;
|
||||
history({search: queryString.stringify(search)});
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [search.employeeTeamId],
|
||||
}}
|
||||
onRow={(record, rowIndex) => {
|
||||
return {
|
||||
onClick: (event) => {
|
||||
handleOnRowClick(record);
|
||||
},
|
||||
};
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
44
client/src/components/shop-teams/shop-teams.container.jsx
Normal file
44
client/src/components/shop-teams/shop-teams.container.jsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import {useQuery} from "@apollo/client";
|
||||
import React from "react";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {QUERY_TEAMS} from "../../graphql/employee_teams.queries";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import ShopEmployeeTeamsListComponent from "./shop-employee-teams.list";
|
||||
import ShopEmployeeTeamsFormComponent from "./shop-employee-teams.form.component";
|
||||
import {Col, Row} from "antd";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
function ShopTeamsContainer({bodyshop}) {
|
||||
const {loading, error, data} = useQuery(QUERY_TEAMS, {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error"/>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<RbacWrapper action="employee_teams:page">
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={6}>
|
||||
<ShopEmployeeTeamsListComponent
|
||||
employee_teams={data ? data.employee_teams : []}
|
||||
loading={loading}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={18}>
|
||||
<ShopEmployeeTeamsFormComponent/>
|
||||
</Col>
|
||||
</Row>
|
||||
</RbacWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(ShopTeamsContainer);
|
||||
@@ -6,7 +6,9 @@ import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {Link, useLocation, useNavigate} from "react-router-dom";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import RomeLogo from "../../assets/RomeOnlineBlue.png";
|
||||
import ImEXOnlineLogo from "../../assets/logo192.png";
|
||||
import InstanceRenderManager from '../../utils/instanceRenderMgr';
|
||||
import {emailSignInStart, sendPasswordReset,} from "../../redux/user/user.actions";
|
||||
import {selectCurrentUser, selectLoginLoading, selectSignInError,} from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -51,7 +53,7 @@ export function SignInComponent({
|
||||
return (
|
||||
<div className="login-container">
|
||||
<div className="login-logo-container">
|
||||
<img src={ImEXOnlineLogo} height="100" width="100" alt="ImEX Online"/>
|
||||
<img src={InstanceRenderManager({imex:ImEXOnlineLogo, rome:RomeLogo})} width={200} alt="Rome Online"/>
|
||||
<Typography.Title>{t("titles.app")}</Typography.Title>
|
||||
</div>
|
||||
<Form onFinish={handleFinish} form={form} size="large">
|
||||
|
||||
@@ -8,6 +8,7 @@ import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
import JobsDetailLaborContainer from "../jobs-detail-labor/jobs-detail-labor.container";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -17,6 +18,13 @@ const mapStateToProps = createStructuredSelector({
|
||||
export function TechClockInComponent({form, bodyshop, technician}) {
|
||||
const {t} = useTranslation();
|
||||
|
||||
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const emps = bodyshop.employees.filter((e) => e.id === technician.id)[0];
|
||||
|
||||
return (
|
||||
@@ -55,7 +63,7 @@ export function TechClockInComponent({form, bodyshop, technician}) {
|
||||
<Select.Option key={item.cost_center} value={item.cost_center}>
|
||||
{item.cost_center === "timetickets.labels.shift"
|
||||
? t(item.cost_center)
|
||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
||||
? t(
|
||||
`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`
|
||||
)
|
||||
|
||||
@@ -12,6 +12,7 @@ import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
|
||||
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
|
||||
import TechClockInComponent from "./tech-job-clock-in-form.component";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician,
|
||||
@@ -29,10 +30,12 @@ export function TechClockInContainer({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
}) {
|
||||
console.log(
|
||||
"🚀 ~ file: tech-job-clock-in-form.container.jsx:30 ~ technician:",
|
||||
technician
|
||||
);
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET, {
|
||||
@@ -76,7 +79,7 @@ export function TechClockInContainer({
|
||||
jobid: values.jobid,
|
||||
cost_center: values.cost_center,
|
||||
ciecacode:
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === 'on'
|
||||
? values.cost_center
|
||||
: Object.keys(
|
||||
bodyshop.md_responsibility_centers.defaults.costs
|
||||
|
||||
@@ -14,6 +14,7 @@ import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {CalculateAllocationsTotals} from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
import TechJobClockoutDelete from "../tech-job-clock-out-delete/tech-job-clock-out-delete.component";
|
||||
import {LaborAllocationContainer} from "../time-ticket-modal/time-ticket-modal.component";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -33,6 +34,12 @@ export function TechClockOffButton({
|
||||
const [updateTimeticket] = useMutation(UPDATE_TIME_TICKET);
|
||||
const [updateJobStatus] = useMutation(UPDATE_JOB_STATUS);
|
||||
const [form] = Form.useForm();
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const {queryLoading, data: lineTicketData} = useQuery(
|
||||
GET_LINE_TICKET_BY_PK,
|
||||
{
|
||||
@@ -66,7 +73,9 @@ export function TechClockOffButton({
|
||||
?.rate,
|
||||
flat_rate: emps && emps.flat_rate,
|
||||
ciecacode:
|
||||
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
|
||||
bodyshop.cdk_dealerid ||
|
||||
bodyshop.pbs_serialnumber ||
|
||||
Enhanced_Payroll.treatment === "on"
|
||||
? values.cost_center
|
||||
: Object.keys(
|
||||
bodyshop.md_responsibility_centers.defaults.costs
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Icon, {ScheduleOutlined, SearchOutlined} from "@ant-design/icons";
|
||||
import Icon, {CarOutlined, ScheduleOutlined, SearchOutlined, UserAddOutlined,} from "@ant-design/icons";
|
||||
import {Layout, Menu} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
@@ -11,23 +11,117 @@ import {createStructuredSelector} from "reselect";
|
||||
import {techLogout} from "../../redux/tech/tech.actions";
|
||||
import {selectTechnician} from "../../redux/tech/tech.selectors";
|
||||
import {BsKanban} from "react-icons/bs";
|
||||
import {useSplitTreatments} from "@splitsoftware/splitio-react";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {setModalContext} from "../../redux/modals/modals.actions";
|
||||
|
||||
const {Sider} = Layout;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
technician: selectTechnician,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
techLogout: () => dispatch(techLogout()),
|
||||
setTimeTicketTaskContext: (context) =>
|
||||
dispatch(setModalContext({context: context, modal: "timeTicketTask"})),
|
||||
});
|
||||
|
||||
export function TechSider({technician, techLogout}) {
|
||||
export function TechSider({
|
||||
technician,
|
||||
techLogout,
|
||||
bodyshop,
|
||||
setTimeTicketTaskContext,
|
||||
}) {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const {t} = useTranslation();
|
||||
const onCollapse = (collapsed) => {
|
||||
setCollapsed(collapsed);
|
||||
};
|
||||
|
||||
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
});
|
||||
|
||||
const items = [
|
||||
{
|
||||
key: "1",
|
||||
icon: <Icon component={FiLogIn}/>,
|
||||
disabled: !!technician,
|
||||
label: <Link to={`/tech/login`}>{t("menus.tech.login")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
icon: <SearchOutlined/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link>,
|
||||
}];
|
||||
|
||||
if (Enhanced_Payroll.treatment === 'on') {
|
||||
items.push({
|
||||
key: 'TechAssignedProdJobs',
|
||||
disabled: !!!technician,
|
||||
icon: <UserAddOutlined/>,
|
||||
label: <Link to={`/tech/assigned`}> {t("menus.tech.assignedjobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
disabled: !!!technician,
|
||||
icon: <Icon component={FaBusinessTime}/>,
|
||||
onClick: () => {
|
||||
setTimeTicketTaskContext({
|
||||
actions: {},
|
||||
context: {jobid: null},
|
||||
});
|
||||
},
|
||||
label: t("menus.tech.claimtask")
|
||||
}
|
||||
)
|
||||
} else {
|
||||
items.push({
|
||||
key: '3',
|
||||
disabled: !!!technician,
|
||||
icon: <Icon component={FaBusinessTime}/>,
|
||||
label: <Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link>
|
||||
})
|
||||
}
|
||||
|
||||
items.push(
|
||||
{
|
||||
key: "4",
|
||||
icon: <Icon component={MdTimer}/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "5",
|
||||
icon: <ScheduleOutlined/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>,
|
||||
},
|
||||
{
|
||||
key: 'dispatchedparts',
|
||||
disabled: !!!technician,
|
||||
icon: <CarOutlined/>,
|
||||
label: <Link to={`/tech/dispatchedparts`}>
|
||||
{t("menus.tech.dispatchedparts")}
|
||||
</Link>
|
||||
},
|
||||
{
|
||||
key: "6",
|
||||
icon: <Icon component={BsKanban}/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/board`}>{t("menus.tech.productionboard")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "7",
|
||||
icon: <Icon component={FiLogOut}/>,
|
||||
disabled: !!!technician,
|
||||
label: t("menus.tech.logout"),
|
||||
});
|
||||
|
||||
return (
|
||||
<Sider
|
||||
style={{
|
||||
@@ -49,50 +143,7 @@ export function TechSider({technician, techLogout}) {
|
||||
techLogout();
|
||||
}
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
key: "1",
|
||||
icon: <Icon component={FiLogIn}/>,
|
||||
disabled: !!technician,
|
||||
label: <Link to={`/tech/login`}>{t("menus.tech.login")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
icon: <SearchOutlined/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/joblookup`}>{t("menus.tech.joblookup")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
icon: <Icon component={FaBusinessTime}/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/jobclock`}>{t("menus.tech.jobclockin")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
icon: <Icon component={MdTimer}/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/shiftclock`}>{t("menus.tech.shiftclockin")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "5",
|
||||
icon: <ScheduleOutlined/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/list`}>{t("menus.tech.productionlist")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "6",
|
||||
icon: <Icon component={BsKanban}/>,
|
||||
disabled: !!!technician,
|
||||
label: <Link to={`/tech/board`}>{t("menus.tech.productionboard")}</Link>,
|
||||
},
|
||||
{
|
||||
key: "7",
|
||||
icon: <Icon component={FiLogOut}/>,
|
||||
disabled: !!!technician,
|
||||
label: t("menus.tech.logout"),
|
||||
},
|
||||
]}
|
||||
items={items}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import {DatePicker} from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import {useNavigate, useLocation} from "react-router-dom";
|
||||
import {useLocation, useNavigate} from "react-router-dom";
|
||||
|
||||
export default function TimeTicketsDatesSelector() {
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
import {DownOutlined} from "@ant-design/icons";
|
||||
import {Button, Checkbox, Col, Form, InputNumber, Popover, Radio, Row, Space, Spin,} from "antd";
|
||||
import React, {useState} from "react";
|
||||
import {GET_JOB_INFO_DRAW_CALCULATIONS} from "../../graphql/jobs-lines.queries";
|
||||
import {useQuery} from "@apollo/client";
|
||||
|
||||
export default function TimeTicketCalculatorComponent({
|
||||
setProductiveHours,
|
||||
|
||||
jobid,
|
||||
}) {
|
||||
const {loading, data: lineTicketData} = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||
variables: {id: jobid},
|
||||
skip: !jobid,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const handleOpenChange = (flag) => setVisible(flag);
|
||||
const handleFinish = ({type, hourstype, percent}) => {
|
||||
//setProductiveHours(values);
|
||||
//setVisible(false);
|
||||
const eligibleHours = Array.isArray(hourstype)
|
||||
? lineTicketData.joblines.reduce(
|
||||
(acc, val) =>
|
||||
acc + (hourstype.includes(val.mod_lbr_ty) ? val.mod_lb_hrs : 0),
|
||||
0
|
||||
)
|
||||
: lineTicketData.joblines.reduce(
|
||||
(acc, val) =>
|
||||
acc + (hourstype === val.mod_lbr_ty ? val.mod_lb_hrs : 0),
|
||||
0
|
||||
);
|
||||
if (type === "draw") {
|
||||
setProductiveHours(eligibleHours * (percent / 100));
|
||||
} else if (type === "cut") {
|
||||
setProductiveHours(eligibleHours * (percent / 100));
|
||||
console.log(
|
||||
"Cut selected, rate set to: ",
|
||||
lineTicketData.jobs_by_pk[`rate_${hourstype.toLowerCase()}`]
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const popContent = (
|
||||
<Spin spinning={loading}>
|
||||
<Form onFinish={handleFinish}>
|
||||
<Form.Item name="type">
|
||||
<Radio.Group>
|
||||
<Radio.Button value="draw">Draw</Radio.Button>
|
||||
<Radio.Button value="cut">Cut of Sale</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item noStyle shouldUpdate>
|
||||
{({getFieldValue}) => (
|
||||
<Form.Item name="hourstype">
|
||||
{getFieldValue("type") === "draw" ? (
|
||||
<Checkbox.Group>
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<Checkbox value="LAB" style={{lineHeight: "32px"}}>
|
||||
Body
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="LAR" style={{lineHeight: "32px"}}>
|
||||
Refinish
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="LAM" style={{lineHeight: "32px"}}>
|
||||
Mechanical
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="LAF" style={{lineHeight: "32px"}}>
|
||||
Frame
|
||||
</Checkbox>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Checkbox value="LAG" style={{lineHeight: "32px"}}>
|
||||
Glass
|
||||
</Checkbox>
|
||||
</Col>
|
||||
</Row>
|
||||
</Checkbox.Group>
|
||||
) : (
|
||||
<Radio.Group>
|
||||
<Radio value="LAB">Body</Radio>
|
||||
|
||||
<Radio value="LAR">Refinish</Radio>
|
||||
|
||||
<Radio value="LAM">Mechanical</Radio>
|
||||
|
||||
<Radio value="LAF">Frame</Radio>
|
||||
|
||||
<Radio value="LAG">Glass</Radio>
|
||||
</Radio.Group>
|
||||
)}
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="percent">
|
||||
<InputNumber min={0} max={100} precision={1} addonAfter="%"/>
|
||||
</Form.Item>
|
||||
<Button htmlType="submit">Calculate</Button>
|
||||
</Form>
|
||||
</Spin>
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={popContent}
|
||||
trigger={["click"]}
|
||||
open={visible}
|
||||
onOpenChange={handleOpenChange}
|
||||
placement="right"
|
||||
destroyTooltipOnHide
|
||||
>
|
||||
<Button onClick={(e) => e.preventDefault()}>
|
||||
<Space>
|
||||
Draw Calculator
|
||||
<DownOutlined/>
|
||||
</Space>
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
import {useQuery} from "@apollo/client";
|
||||
import {Button, Form, InputNumber, Modal, Radio, Select, Space, Table, Typography,} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React, {useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {GET_JOB_INFO_DRAW_CALCULATIONS} from "../../graphql/jobs-lines.queries";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(TimeTicketListTeamPay);
|
||||
|
||||
export function TimeTicketListTeamPay({bodyshop, context, actions}) {
|
||||
//const { refetch } = actions;
|
||||
const {jobId} = context;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const {t} = useTranslation();
|
||||
const {
|
||||
//loading,
|
||||
data: lineTicketData,
|
||||
} = useQuery(GET_JOB_INFO_DRAW_CALCULATIONS, {
|
||||
variables: {id: jobId},
|
||||
skip: !jobId,
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
});
|
||||
|
||||
const handleOk = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
width={"80%"}
|
||||
open={visible}
|
||||
destroyOnClose
|
||||
onOk={handleOk}
|
||||
onCancel={() => setVisible(false)}
|
||||
>
|
||||
<Form layout="vertical" form={form} initialValues={{jobid: jobId}}>
|
||||
<LayoutFormRow grow noDivider>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => (
|
||||
<Form.Item
|
||||
name="jobid"
|
||||
label={t("timetickets.fields.ro_number")}
|
||||
rules={[
|
||||
{
|
||||
required: !(
|
||||
form.getFieldValue("cost_center") ===
|
||||
"timetickets.labels.shift"
|
||||
),
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<JobSearchSelectComponent
|
||||
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
|
||||
notExported={!bodyshop.tt_allow_post_to_invoiced}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("timetickets.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow grow noDivider>
|
||||
<Form.Item name="team" label="Team">
|
||||
<Select
|
||||
options={Teams.map((team) => ({
|
||||
value: team.name,
|
||||
label: team.name,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="hourstype">
|
||||
<Radio.Group>
|
||||
<Radio value="LAB">Body</Radio>
|
||||
<Radio value="LAR">Refinish</Radio>
|
||||
<Radio value="LAM">Mechanical</Radio>
|
||||
<Radio value="LAF">Frame</Radio>
|
||||
<Radio value="LAG">Glass</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="percent">
|
||||
<InputNumber min={0} max={100} precision={1} addonAfter="%"/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{({getFieldsValue}) => {
|
||||
const formData = getFieldsValue();
|
||||
|
||||
let data = [];
|
||||
let eligibleHours = 0;
|
||||
const theTeam = Teams.find((team) => team.name === formData.team);
|
||||
if (theTeam) {
|
||||
eligibleHours =
|
||||
lineTicketData.joblines.reduce(
|
||||
(acc, val) =>
|
||||
acc +
|
||||
(formData.hourstype === val.mod_lbr_ty
|
||||
? val.mod_lb_hrs
|
||||
: 0),
|
||||
0
|
||||
) * (formData.percent / 100 || 0);
|
||||
|
||||
data = theTeam.employees.map((e) => {
|
||||
return {
|
||||
employeeid: e.employeeid,
|
||||
percentage: e.percentage,
|
||||
rate: e.rates[formData.hourstype],
|
||||
cost_center:
|
||||
bodyshop.md_responsibility_centers.defaults.costs[
|
||||
formData.hourstype
|
||||
],
|
||||
productivehrs:
|
||||
Math.round(eligibleHours * 100 * (e.percentage / 100)) /
|
||||
100,
|
||||
pay: Dinero({
|
||||
amount: Math.round(
|
||||
(e.rates[formData.hourstype] || 0) * 100
|
||||
),
|
||||
})
|
||||
.multiply(
|
||||
Math.round(eligibleHours * 100 * (e.percentage / 100)) /
|
||||
100
|
||||
)
|
||||
.toFormat("$0.00"),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Table
|
||||
dataSource={data}
|
||||
rowKey={"employeeid"}
|
||||
title={() => (
|
||||
<Space>
|
||||
<Typography.Title level={4}>
|
||||
Tickets to be Created
|
||||
</Typography.Title>
|
||||
<span>{`(${eligibleHours} hours to split)`}</span>
|
||||
</Space>
|
||||
)}
|
||||
columns={[
|
||||
{
|
||||
title: t("timetickets.fields.employee"),
|
||||
dataIndex: "employee",
|
||||
key: "employee",
|
||||
render: (text, record) => {
|
||||
const emp = bodyshop.employees.find(
|
||||
(e) => e.id === record.employeeid
|
||||
);
|
||||
return `${emp?.first_name} ${emp?.last_name}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.cost_center"),
|
||||
dataIndex: "cost_center",
|
||||
key: "cost_center",
|
||||
|
||||
render: (text, record) =>
|
||||
record.cost_center === "timetickets.labels.shift"
|
||||
? t(record.cost_center)
|
||||
: record.cost_center,
|
||||
},
|
||||
{
|
||||
title: t("timetickets.fields.productivehrs"),
|
||||
dataIndex: "productivehrs",
|
||||
key: "productivehrs",
|
||||
},
|
||||
{
|
||||
title: "Percentage",
|
||||
dataIndex: "percentage",
|
||||
key: "percentage",
|
||||
},
|
||||
{
|
||||
title: "Rate",
|
||||
dataIndex: "rate",
|
||||
key: "rate",
|
||||
},
|
||||
{
|
||||
title: "Pay",
|
||||
dataIndex: "pay",
|
||||
key: "pay",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Button onClick={() => setVisible(true)}>Assign Team Pay </Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const Teams = [
|
||||
{
|
||||
name: "Team A",
|
||||
employees: [
|
||||
{
|
||||
employeeid: "9f1bdc23-8dc2-4b6a-8ca1-5f83a6fdcc22",
|
||||
percentage: 50,
|
||||
rates: {
|
||||
LAB: 10,
|
||||
LAR: 15,
|
||||
},
|
||||
},
|
||||
{
|
||||
employeeid: "201db66c-96c7-41ec-bed4-76842ba93087",
|
||||
percentage: 50,
|
||||
rates: {
|
||||
LAB: 20,
|
||||
LAR: 25,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Team B",
|
||||
employees: [
|
||||
{
|
||||
employeeid: "9f1bdc23-8dc2-4b6a-8ca1-5f83a6fdcc22",
|
||||
percentage: 75,
|
||||
rates: {
|
||||
LAB: 100,
|
||||
LAR: 150,
|
||||
},
|
||||
},
|
||||
{
|
||||
employeeid: "201db66c-96c7-41ec-bed4-76842ba93087",
|
||||
percentage: 25,
|
||||
rates: {
|
||||
LAB: 200,
|
||||
LAR: 250,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user