Merge branch 'master-AIO' into feature/IO-2458-RO-Closer

This commit is contained in:
Patrick Fic
2024-03-22 08:41:12 -07:00
63 changed files with 1754 additions and 2657 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -14791,6 +14791,27 @@
<folder_node> <folder_node>
<name>titles</name> <name>titles</name>
<children> <children>
<concept_node>
<name>joblifecycle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>labhours</name> <name>labhours</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -37126,6 +37147,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>parts</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>partssublet</name> <name>partssublet</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

1613
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,48 +12,34 @@
"@ant-design/pro-layout": "^7.17.16", "@ant-design/pro-layout": "^7.17.16",
"@apollo/client": "^3.8.10", "@apollo/client": "^3.8.10",
"@asseinfo/react-kanban": "^2.2.0", "@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^7.1.0",
"@fingerprintjs/fingerprintjs": "^4.2.2", "@fingerprintjs/fingerprintjs": "^4.2.2",
"@jsreport/browser-client": "^3.1.0", "@jsreport/browser-client": "^3.1.0",
"@reduxjs/toolkit": "^2.2.1", "@reduxjs/toolkit": "^2.2.1",
"@sentry/cli": "^2.28.6", "@sentry/cli": "^2.28.6",
"@sentry/react": "^7.104.0", "@sentry/react": "^7.104.0",
"@sentry/tracing": "^7.104.0",
"@splitsoftware/splitio-react": "^1.11.0", "@splitsoftware/splitio-react": "^1.11.0",
"@tanem/react-nprogress": "^5.0.51", "@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-legacy": "^5.3.0",
"@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react": "^4.2.1",
"@vitejs/plugin-react-refresh": "^1.3.6", "antd": "^5.15.3",
"@vitejs/plugin-react-swc": "^3.6.0",
"antd": "^5.14.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0", "apollo-link-sentry": "^3.3.0",
"axios": "^1.6.7", "axios": "^1.6.7",
"consola": "^3.2.3",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"dayjs-business-days2": "^1.2.2", "dayjs-business-days2": "^1.2.2",
"dinero.js": "^1.9.1", "dinero.js": "^1.9.1",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"esbuild": "^0.20.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^10.8.1", "firebase": "^10.8.1",
"graphql": "^16.6.0", "graphql": "^16.6.0",
"i18next": "^23.10.0", "i18next": "^23.10.0",
"i18next-browser-languagedetector": "^7.0.2", "i18next-browser-languagedetector": "^7.0.2",
"jsoneditor": "^10.0.1",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.57", "libphonenumber-js": "^1.10.57",
"logrocket": "^8.0.1", "logrocket": "^8.0.1",
"markerjs2": "^2.32.0", "markerjs2": "^2.32.0",
"normalize-url": "^8.0.0", "normalize-url": "^8.0.0",
"phone": "^3.1.42",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"query-string": "^9.0.0", "query-string": "^9.0.0",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^18.2.0", "react": "^18.2.0",
"react-big-calendar": "^1.11.0", "react-big-calendar": "^1.11.0",
"react-color": "^2.19.3", "react-color": "^2.19.3",
@@ -65,16 +51,15 @@
"react-i18next": "^14.0.5", "react-i18next": "^14.0.5",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-image-lightbox": "^5.1.4", "react-image-lightbox": "^5.1.4",
"react-intersection-observer": "^9.8.1",
"react-joyride": "^2.7.4", "react-joyride": "^2.7.4",
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-number-format": "^5.3.3", "react-number-format": "^5.3.3",
"react-product-fruits": "^2.2.6",
"react-redux": "^9.1.0", "react-redux": "^9.1.0",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5",
"react-router-dom": "^6.22.2", "react-router-dom": "^6.22.2",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"react-sticky": "^6.0.3", "react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.5", "react-virtualized": "^9.22.5",
"recharts": "^2.12.2", "recharts": "^2.12.2",
"redux": "^5.0.1", "redux": "^5.0.1",
@@ -87,17 +72,15 @@
"styled-components": "^6.1.8", "styled-components": "^6.1.8",
"subscriptions-transport-ws": "^0.11.0", "subscriptions-transport-ws": "^0.11.0",
"terser-webpack-plugin": "^5.3.10", "terser-webpack-plugin": "^5.3.10",
"vite-plugin-compression": "^0.5.1", "userpilot": "^1.3.1",
"vite-plugin-ejs": "^1.7.0", "vite-plugin-ejs": "^1.7.0",
"vite-plugin-svgr": "^4.2.0",
"web-vitals": "^3.5.2", "web-vitals": "^3.5.2",
"workbox-core": "^7.0.0", "workbox-core": "^7.0.0",
"workbox-expiration": "^7.0.0", "workbox-expiration": "^7.0.0",
"workbox-navigation-preload": "^7.0.0", "workbox-navigation-preload": "^7.0.0",
"workbox-precaching": "^7.0.0", "workbox-precaching": "^7.0.0",
"workbox-routing": "^7.0.0", "workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0", "workbox-strategies": "^7.0.0"
"yauzl": "^3.1.1"
}, },
"scripts": { "scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'", "analyze": "source-map-explorer 'build/static/js/*.js'",
@@ -152,7 +135,6 @@
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
"browserslist": "^4.22.3", "browserslist": "^4.22.3",
"browserslist-to-esbuild": "^2.1.1", "browserslist-to-esbuild": "^2.1.1",
"craco-less": "^3.0.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"cypress": "^13.6.6", "cypress": "^13.6.6",
"eslint-plugin-cypress": "^2.15.1", "eslint-plugin-cypress": "^2.15.1",

View File

@@ -12,6 +12,12 @@ import App from "./App";
import * as Sentry from "@sentry/react"; import * as Sentry from "@sentry/react";
import themeProvider from "./themeProvider"; import themeProvider from "./themeProvider";
import { Userpilot } from 'userpilot'
// Initialize Userpilot
if(import.meta.env.DEV){
Userpilot.initialize('NX-69145f08');
}
dayjs.locale("en"); dayjs.locale("en");

View File

@@ -27,6 +27,8 @@ import "./App.styles.scss";
import handleBeta from "../utils/betaHandler"; import handleBeta from "../utils/betaHandler";
import Eula from "../components/eula/eula.component"; import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr"; import InstanceRenderMgr from "../utils/instanceRenderMgr";
import { ProductFruits } from 'react-product-fruits';
const ResetPassword = lazy(() => const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component") import("../pages/reset-password/reset-password.component")
); );
@@ -149,6 +151,7 @@ export function App({
// Any route that is not assigned and matched will default to the Landing Page component // Any route that is not assigned and matched will default to the Landing Page component
return ( return (
<Suspense <Suspense
fallback={ fallback={
<LoadingSpinner <LoadingSpinner
@@ -160,6 +163,12 @@ export function App({
/> />
} }
> >
<ProductFruits //workspaceCode="aoJoEifvezYI0Z0P"
language="en" user={{
email: currentUser.email,
username: currentUser.email,
}} />
<Routes> <Routes>
<Route <Route
path="*" path="*"

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,5 +1,5 @@
import {useMutation, useQuery} from "@apollo/client"; import {useMutation, useQuery} from "@apollo/client";
import {Button, Form, Popconfirm, Space} from "antd"; import {Button, Divider, Form, Popconfirm, Space} from "antd";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import queryString from "query-string"; import queryString from "query-string";
import React, {useState} from "react"; import React, {useState} from "react";
@@ -203,7 +203,7 @@ export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail,
layout="vertical" layout="vertical"
> >
<BillFormContainer form={form} billEdit disabled={exported}/> <BillFormContainer form={form} billEdit disabled={exported}/>
<Divider orientation="left">{t("general.labels.media")}</Divider>
{bodyshop.uselocalmediaserver ? ( {bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery <JobsDocumentsLocalGallery
job={{id: data ? data.bills_by_pk.jobid : null}} job={{id: data ? data.bills_by_pk.jobid : null}}

View File

@@ -173,7 +173,11 @@ export function BillDetailEditReturn({
</Form> </Form>
</Modal> </Modal>
<Button <Button
disabled={data.bills_by_pk.is_credit_memo || disabled} disabled={
data.bills_by_pk.is_credit_memo ||
data.bills_by_pk.isinhouse ||
disabled
}
onClick={() => { onClick={() => {
setOpen(true); setOpen(true);
}} }}

View File

@@ -172,6 +172,7 @@ function BillEnterModalContainer({
], ],
}, },
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"], refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID", "GET_JOB_BY_PK"],
awaitRefetchQueries: true
}); });
await Promise.all( await Promise.all(
@@ -239,6 +240,7 @@ function BillEnterModalContainer({
if (markPolReceived && markPolReceived.length > 0) { if (markPolReceived && markPolReceived.length > 0) {
const r2 = await updatePartsOrderLines({ const r2 = await updatePartsOrderLines({
variables: {partsLineIds: markPolReceived.map((p) => p.id)}, variables: {partsLineIds: markPolReceived.map((p) => p.id)},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID" ],
}); });
if (!!r2.errors) { if (!!r2.errors) {
setLoading(false); setLoading(false);
@@ -373,12 +375,13 @@ function BillEnterModalContainer({
}); });
if (enterAgain) { if (enterAgain) {
// form.resetFields(); form.resetFields();
form.setFieldsValue({ form.setFieldsValue({
...formValues, ...formValues,
billlines: [], vendorid:values.vendorid,
}); billlines: [],
form.resetFields(); });
// form.resetFields();
} else { } else {
toggleModalVisible(); toggleModalVisible();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -689,14 +689,14 @@ export function BillEnterModalLinesComponent({
formItemProps: (field) => { formItemProps: (field) => {
return { return {
key: `${field.index}fedtax`, key: `${field.index}fedtax`,
valuePropName: "checked", valuePropName: 'checked',
// initialValue: true, initialValue: InstanceRenderManager({
name: [ imex: true,
field.name, rome: false,
"applicable_taxes", promanager: false,
"federal", }),
], name: [field.name, 'applicable_taxes', 'federal'],
}; };
}, },
formInput: (record, index) => ( formInput: (record, index) => (

View File

@@ -12,6 +12,7 @@ import "./chat-affix.styles.scss";
export function ChatAffixContainer({bodyshop, chatVisible}) { export function ChatAffixContainer({bodyshop, chatVisible}) {
const {t} = useTranslation(); const {t} = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
useEffect(() => { useEffect(() => {
if (!bodyshop || !bodyshop.messagingservicesid) return; if (!bodyshop || !bodyshop.messagingservicesid) return;
@@ -31,6 +32,7 @@ export function ChatAffixContainer({bodyshop, chatVisible}) {
error error
); );
notification.open({ notification.open({
key: 'fcm',
type: "warning", type: "warning",
message: t("general.errors.fcm"), message: t("general.errors.fcm"),
btn: ( btn: (
@@ -62,7 +64,7 @@ export function ChatAffixContainer({bodyshop, chatVisible}) {
SubscribeToTopic(); SubscribeToTopic();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, [bodyshop]);
useEffect(() => { useEffect(() => {
function handleMessage(payload) { function handleMessage(payload) {

View File

@@ -61,6 +61,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
value: "courtesycars.status.in", value: "courtesycars.status.in",
}, },
{ {
text: t("courtesycars.status.inservice"),
value: "courtesycars.status.inservice",
},
{
text: t("courtesycars.status.out"), text: t("courtesycars.status.out"),
value: "courtesycars.status.out", value: "courtesycars.status.out",
}, },
@@ -73,7 +77,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
value: "courtesycars.status.leasereturn", value: "courtesycars.status.leasereturn",
}, },
], ],
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => record.status === value,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -176,7 +180,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
title: t("courtesycars.fields.fuel"), title: t("courtesycars.fields.fuel"),
dataIndex: "fuel", dataIndex: "fuel",
key: "fuel", key: "fuel",
sorter: (a, b) => alphaSort(a.fuel, b.fuel), sorter: (a, b) => a.fuel - b.fuel,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order, state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -185,12 +189,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
return t("courtesycars.labels.fuel.full"); return t("courtesycars.labels.fuel.full");
case 88: case 88:
return t("courtesycars.labels.fuel.78"); return t("courtesycars.labels.fuel.78");
case 63: case 75:
return t("courtesycars.labels.fuel.34");
case 63:
return t("courtesycars.labels.fuel.58"); return t("courtesycars.labels.fuel.58");
case 50: case 50:
return t("courtesycars.labels.fuel.12"); return t("courtesycars.labels.fuel.12");
case 38: case 38:
return t("courtesycars.labels.fuel.34"); return t("courtesycars.labels.fuel.38");
case 25: case 25:
return t("courtesycars.labels.fuel.14"); return t("courtesycars.labels.fuel.14");
case 13: case 13:

View File

@@ -9,6 +9,7 @@ import axios from "axios";
const fortyFiveDaysAgo = () => dayjs().subtract(45, 'day').toLocaleString(); const fortyFiveDaysAgo = () => dayjs().subtract(45, 'day').toLocaleString();
export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) { export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) {
console.log("🚀 ~ JobLifecycleDashboardComponent ~ bodyshop:", bodyshop)
const {t} = useTranslation(); const {t} = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [lifecycleData, setLifecycleData] = useState(null); const [lifecycleData, setLifecycleData] = useState(null);
@@ -19,7 +20,7 @@ export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardP
setLoading(true); setLoading(true);
const response = await axios.post("/job/lifecycle", { const response = await axios.post("/job/lifecycle", {
jobids: data.job_lifecycle.map(x => x.id), jobids: data.job_lifecycle.map(x => x.id),
statuses: bodyshop.md_order_statuses statuses: bodyshop.md_ro_statuses
}); });
setLifecycleData(response.data.durations); setLifecycleData(response.data.durations);
setLoading(false); setLoading(false);

View File

@@ -184,7 +184,7 @@ export function DashboardGridComponent({currentUser, bodyshop}) {
}} }}
onClick={() => handleRemoveComponent(item.i)} onClick={() => handleRemoveComponent(item.i)}
/> />
<TheComponent className="dashboard-card" data={dashboarddata}/> <TheComponent className="dashboard-card" bodyshop={bodyshop} data={dashboarddata}/>
</LoadingSkeleton> </LoadingSkeleton>
</div> </div>
); );

View File

@@ -1,23 +1,17 @@
import dayjs from "../../utils/day"; import dayjs from '../../utils/day';
import React from "react"; import React from 'react';
import { useTranslation } from "react-i18next"; import { useTranslation } from 'react-i18next';
import { connect } from "react-redux"; import { connect } from 'react-redux';
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from 'reselect';
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from '../../redux/user/user.selectors';
import AlertComponent from "../alert/alert.component"; import AlertComponent from '../alert/alert.component';
import InstanceRenderManager from '../../utils/instanceRenderMgr'; import InstanceRenderManager from '../../utils/instanceRenderMgr';
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
function FeatureWrapper({ function FeatureWrapper({ bodyshop, featureName, noauth, children, ...restProps }) {
bodyshop,
featureName,
noauth,
children,
...restProps
}) {
const { t } = useTranslation(); const { t } = useTranslation();
if (HasFeatureAccess({ featureName, bodyshop })) return children; if (HasFeatureAccess({ featureName, bodyshop })) return children;
@@ -25,7 +19,13 @@ function FeatureWrapper({
return ( return (
noauth || ( noauth || (
<AlertComponent <AlertComponent
message={t("general.messages.nofeatureaccess", {app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})})} message={t('general.messages.nofeatureaccess', {
app: InstanceRenderManager({
imex: '$t(titles.imexonline)',
rome: '$t(titles.romeonline)',
promanager: '$t(titles.promanager)',
}),
})}
type="warning" type="warning"
/> />
) )
@@ -33,10 +33,7 @@ function FeatureWrapper({
} }
export function HasFeatureAccess({ featureName, bodyshop }) { export function HasFeatureAccess({ featureName, bodyshop }) {
return ( return bodyshop?.features.allAccess || dayjs(bodyshop?.features[featureName]).isAfter(dayjs());
bodyshop?.features.allAccess ||
dayjs(bodyshop?.features[featureName]).isAfter(dayjs())
);
} }
export default connect(mapStateToProps, null)(FeatureWrapper); export default connect(mapStateToProps, null)(FeatureWrapper);

View File

@@ -5,6 +5,7 @@ import {useTranslation} from "react-i18next";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./job-bills-total.styles.scss"; import "./job-bills-total.styles.scss";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function JobBillsTotalComponent({ export default function JobBillsTotalComponent({
loading, loading,
@@ -89,7 +90,7 @@ export default function JobBillsTotalComponent({
.add(Dinero(totals.parts.sublets.total)) .add(Dinero(totals.parts.sublets.total))
.add(Dinero(totals.additional.shipping)) .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? .add( InstanceRenderManager({imex: Dinero(), rome: Dinero(totals.additional.additionalCosts),promanager: "USE_ROME" })) ; // Additional costs were captured for Rome, but not imex.
const discrepancy = totalPartsSublet.subtract(billTotals); const discrepancy = totalPartsSublet.subtract(billTotals);

View File

@@ -290,7 +290,7 @@ export function JobLinesComponent({
key: 'location', key: 'location',
render: (text, record) => <JobLineLocationPopup jobline={record} disabled={jobRO} />, render: (text, record) => <JobLineLocationPopup jobline={record} disabled={jobRO} />,
}, },
...(HasFeatureAccess({ featureName: 'bills' }) ...(HasFeatureAccess({ featureName: 'bills', bodyshop })
? [ ? [
{ {
title: t('joblines.labels.billref'), title: t('joblines.labels.billref'),

View File

@@ -23,7 +23,6 @@ export function JobEmployeeAssignments({
jobRO, jobRO,
body, body,
refinish, refinish,
prep, prep,
csr, csr,
handleAdd, handleAdd,
@@ -78,7 +77,7 @@ export function JobEmployeeAssignments({
setVisibility(false); setVisibility(false);
}} }}
> >
Assign {t("allocations.actions.assign")}
</Button> </Button>
<Button onClick={() => setVisibility(false)}>Close</Button> <Button onClick={() => setVisibility(false)}>Close</Button>
</Space> </Space>

View File

@@ -44,13 +44,13 @@ export function JobEmployeeAssignmentsContainer({
}); });
if (refetch) refetch(); if (refetch) refetch();
insertAuditTrail({ if (!!!result.errors) {
jobid: job.id, insertAuditTrail({
operation: AuditTrailMapping.jobassignmentchange(operation, name), jobid: job.id,
type: "jobassignmentchange", operation: AuditTrailMapping.jobassignmentchange(operation, name),
}); type: "jobassignmentchange",
});
if (!!result.errors) { } else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.assigning", { message: t("jobs.errors.assigning", {
message: JSON.stringify(result.errors), message: JSON.stringify(result.errors),
@@ -68,19 +68,21 @@ export function JobEmployeeAssignmentsContainer({
variables: {jobId: job.id, job: {[empAssignment]: null}}, variables: {jobId: job.id, job: {[empAssignment]: null}},
}); });
if (!!result.errors) { if (!!!result.errors) {
notification["error"]({ insertAuditTrail({
message: t("jobs.errors.assigning", { jobid: job.id,
message: JSON.stringify(result.errors), operation: AuditTrailMapping.jobassignmentremoved(operation),
}), type: "jobassignmentremoved",
}); });
} } else {
insertAuditTrail({ notification["error"]({
jobid: job.id, message: t("jobs.errors.assigning", {
operation: AuditTrailMapping.jobassignmentremoved(operation), message: JSON.stringify(result.errors),
type: "jobassignmentremoved",}); }),
setLoading(false); });
}; }
setLoading(false);
};
return ( return (
<div> <div>

View File

@@ -82,7 +82,7 @@ export default function JobReconciliationBillsTable({
state.sortedInfo.order, state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Checkbox disabled checked={record.bill.is_credit_memo}/> <Checkbox checked={record.bill.is_credit_memo}/>
), ),
}, },
]; ];

View File

@@ -6,6 +6,7 @@ import {useTranslation} from "react-i18next";
import {connect} from "react-redux"; import {connect} from "react-redux";
import {createStructuredSelector} from "reselect"; import {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import InstanceRenderManager from '../../utils/instanceRenderMgr';
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -29,108 +30,130 @@ export function JobTotalsTableTotals({bodyshop, job}) {
total: job.job_totals.totals.subtotal, total: job.job_totals.totals.subtotal,
bold: true, bold: true,
}, },
...InstanceRenderManager({imex: [ {
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"
? [
{
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
]
: []),
{
key: t("jobs.labels.federal_tax_amt"),
total: job.job_totals.totals.federal_tax,
},],
promanager: "USE_ROME",
rome: [(job.job_totals.totals.us_sales_tax_breakdown
? [
{
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: `${
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,
},
])]
}),
...(job.job_totals.totals.us_sales_tax_breakdown
? [
{
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: `${
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"), key: t("jobs.labels.total_repairs"),

View File

@@ -6,6 +6,7 @@ import {connect} from "react-redux";
import {createStructuredSelector} from "reselect"; import {createStructuredSelector} from "reselect";
import {logImEXEvent} from "../../firebase/firebase.utils"; import {logImEXEvent} from "../../firebase/firebase.utils";
import {selectBodyshop} from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -29,19 +30,25 @@ export function JobsCloseAutoAllocate({bodyshop, joblines, form, disabled}) {
ret.profitcenter_labor = null; ret.profitcenter_labor = null;
} }
//Verify that this is also manually updated in server/job-costing //Verify that this is also manually updated in server/job-costing
if (!jl.part_type && !jl.mod_lbr_ty) { if (
const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : ""; InstanceRenderManager({
if (lineDesc.includes("shop materials")) { imex: !jl.part_type && !jl.mod_lbr_ty,
ret.profitcenter_part = defaults.profits["MASH"]; rome: !ret.profitcenter_part,
} else if (lineDesc.includes("paint/materials")) { promanager: 'USE_ROME',
ret.profitcenter_part = defaults.profits["MAPA"]; })
} else if (lineDesc.includes("ats amount")) { ) {
ret.profitcenter_part = defaults.profits["ATS"]; const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : '';
} else if (jl.act_price > 0) { if (lineDesc.includes('shop materials')) {
ret.profitcenter_part = defaults.profits["PAO"]; ret.profitcenter_part = defaults.profits['MASH'];
} else { } else if (lineDesc.includes('paint/materials')) {
ret.profitcenter_part = null; 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;
}
} }
return ret; return ret;
}), }),

View File

@@ -1,261 +1,225 @@
import {Divider, Form, Input, InputNumber, Select, Space, Switch, Tooltip,} from "antd"; import { Divider, Form, Input, InputNumber, Select, Space, Switch, Tooltip } from 'antd';
import React from "react"; import React from 'react';
import {useTranslation} from "react-i18next"; import { useTranslation } from 'react-i18next';
import {connect} from "react-redux"; import { connect } from 'react-redux';
import {createStructuredSelector} from "reselect"; import { createStructuredSelector } from 'reselect';
import {selectJobReadOnly} from "../../redux/application/application.selectors"; import { selectJobReadOnly } from '../../redux/application/application.selectors';
import {selectBodyshop} from "../../redux/user/user.selectors"; import { selectBodyshop } from '../../redux/user/user.selectors';
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component"; import CABCpvrtCalculator from '../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component';
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; 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 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 JobsMarkPstExempt from '../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component';
import FormRow from "../layout-form-row/layout-form-row.component"; import FormRow from '../layout-form-row/layout-form-row.component';
import JobsDetailRatesLabor from "./jobs-detail-rates.labor.component"; import JobsDetailRatesLabor from './jobs-detail-rates.labor.component';
import JobsDetailRatesMaterials from "./jobs-detail-rates.materials.component"; import JobsDetailRatesMaterials from './jobs-detail-rates.materials.component';
import JobsDetailRatesOther from "./jobs-detail-rates.other.component"; import JobsDetailRatesOther from './jobs-detail-rates.other.component';
import JobsDetailRatesParts from "./jobs-detail-rates.parts.component"; import JobsDetailRatesParts from './jobs-detail-rates.parts.component';
import JobsDetailRatesTaxes from "./jobs-detail-rates.taxes.component"; import JobsDetailRatesTaxes from './jobs-detail-rates.taxes.component';
import JobsDetailRatesProfileOVerride from "./jobs-detail-rates.profile-override.component"; import JobsDetailRatesProfileOVerride from './jobs-detail-rates.profile-override.component';
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from '../../utils/instanceRenderMgr';
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
export function JobsDetailRates({jobRO, form, job, bodyshop}) { export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
const {t} = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <div>
<FormRow> <FormRow>
<Form.Item label={t("jobs.fields.class")} name="class"> <Form.Item label={t('jobs.fields.class')} name="class">
<Select disabled={true}/> <Select disabled={true} />
</Form.Item>
<Form.Item
label={t("jobs.fields.depreciation_taxes")}
name="depreciation_taxes"
>
<CurrencyInput disabled={jobRO} min={0}/>
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Form.Item
label={t("jobs.fields.ca_customer_gst")}
name="ca_customer_gst"
>
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round(
(job.job_totals &&
job.job_totals.totals.federal_tax.amount) ||
0
) / 100
}
/>
</Form.Item>
</Tooltip>
)}
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"
>
<CurrencyInput disabled={jobRO} min={0}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.towing_payable")}
name="towing_payable"
>
<CurrencyInput disabled={jobRO} min={0}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.storage_payable")}
name="storage_payable"
>
<CurrencyInput disabled={jobRO} min={0}/>
</Form.Item>
<Form.Item
label={t("jobs.fields.adjustment_bottom_line")}
name="adjustment_bottom_line"
>
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid}/>
</Form.Item>
{bodyshop.region_config === "CA_BC" && (
<Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0}/>
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO}/>
</Space>
)}
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
valuePropName="checked"
>
<Switch disabled={jobRO}/>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}
>
{() => {
if (form.getFieldValue("auto_add_ats"))
return (
<Form.Item
label={t("jobs.fields.rate_ats")}
name="rate_ats"
initialValue={bodyshop.shoprates.rate_atp}
>
<CurrencyInput disabled={jobRO}/>
</Form.Item>
);
return null;
}}
</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>
<Form.Item label={t('jobs.fields.depreciation_taxes')} name="depreciation_taxes">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith('ca') && (
<Tooltip title={t('jobs.labels.ca_gst_all_if_null')}>
<Form.Item label={t('jobs.fields.ca_customer_gst')} name="ca_customer_gst">
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round((job.job_totals && job.job_totals.totals.federal_tax.amount) || 0) /
100
}
/>
</Form.Item>
</Tooltip>
)}
<Form.Item label={t('jobs.fields.other_amount_payable')} name="other_amount_payable">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item label={t('jobs.fields.towing_payable')} name="towing_payable">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item label={t('jobs.fields.storage_payable')} name="storage_payable">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item label={t('jobs.fields.adjustment_bottom_line')} name="adjustment_bottom_line">
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item>
{bodyshop.region_config === 'CA_BC' && (
<Space align="center">
<Form.Item label={t('jobs.fields.ca_bc_pvrt')} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
)}
<Form.Item <Form.Item
label={t("jobs.fields.state_tax_rate")} label={t('jobs.fields.auto_add_ats')}
name="state_tax_rate" name="auto_add_ats"
valuePropName="checked"
> >
<InputNumber <Switch disabled={jobRO} />
</Form.Item>
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}>
{() => {
if (form.getFieldValue('auto_add_ats'))
return (
<Form.Item
label={t('jobs.fields.rate_ats')}
name="rate_ats"
initialValue={bodyshop.shoprates.rate_atp}
>
<CurrencyInput disabled={jobRO} />
</Form.Item>
);
return null;
}}
</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} min={0}
max={1} max={1}
precision={2} precision={2}
disabled={jobRO} disabled={jobRO}
autoComplete="new-password" autoComplete="new-password"
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t('jobs.fields.local_tax_rate')} name="local_tax_rate">
label={t("jobs.fields.local_tax_rate")} <InputNumber min={0} max={1} precision={2} disabled={jobRO} />
name="local_tax_rate" </Form.Item>
> {bodyshop.region_config.toLowerCase().startsWith('ca') && (
<InputNumber min={0} max={1} precision={2} disabled={jobRO}/> <Form.Item
</Form.Item> label={t('jobs.fields.ca_gst_registrant')}
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant" name="ca_gst_registrant"
valuePropName="checked" valuePropName="checked"
> >
<Switch disabled={jobRO}/> <Switch disabled={jobRO} />
</Form.Item> </Form.Item>
)} )}
</FormRow>}) </FormRow>
} ),
<Divider })}
orientation="left" <Divider orientation="left" type="horizontal" style={{ marginTop: '.8rem', float: 'right' }}>
type="horizontal" {t('jobs.forms.laborrates')}
style={{marginTop: ".8rem", float: "right"}} </Divider>
> <Space>
{t("jobs.forms.laborrates")} <JobsDetailRatesChangeButton form={form} disabled={jobRO} />
</Divider> <JobsMarkPstExempt form={form} />
<Space> </Space>
<JobsDetailRatesChangeButton form={form} disabled={jobRO}/> <FormRow noDivider>
<JobsMarkPstExempt form={form}/> <Form.Item label={t('jobs.fields.labor_rate_desc')} name="labor_rate_desc">
</Space> <Input disabled={jobRO} />
<FormRow noDivider> </Form.Item>
<Form.Item <Form.Item label={t('jobs.fields.rate_laa')} name="rate_laa">
label={t("jobs.fields.labor_rate_desc")} <CurrencyInput min={0} disabled={jobRO} />
name="labor_rate_desc" </Form.Item>
> <Form.Item label={t('jobs.fields.rate_lab')} name="rate_lab">
<Input disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa"> <Form.Item label={t('jobs.fields.rate_lad')} name="rate_lad">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab"> <Form.Item label={t('jobs.fields.rate_lae')} name="rate_lae">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad"> <Form.Item label={t('jobs.fields.rate_lar')} name="rate_lar">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae"> <Form.Item label={t('jobs.fields.rate_las')} name="rate_las">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar"> <Form.Item label={t('jobs.fields.rate_laf')} name="rate_laf">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las"> <Form.Item label={t('jobs.fields.rate_lam')} name="rate_lam">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf"> <Form.Item label={t('jobs.fields.rate_lag')} name="rate_lag">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam"> <Form.Item label={t('jobs.fields.rate_la1')} name="rate_la1">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag"> <Form.Item label={t('jobs.fields.rate_la2')} name="rate_la2">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1"> <Form.Item label={t('jobs.fields.rate_la3')} name="rate_la3">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2"> <Form.Item label={t('jobs.fields.rate_la4')} name="rate_la4">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3"> <Form.Item label={t('jobs.fields.rate_lau')} name="rate_lau">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4"> <Form.Item label={t('jobs.fields.rate_mapa')} name="rate_mapa">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau"> <Form.Item label={t('jobs.fields.rate_mash')} name="rate_mash">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa"> <Form.Item label={t('jobs.fields.rate_mahw')} name="rate_mahw">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash"> <Form.Item label={t('jobs.fields.rate_ma2s')} name="rate_ma2s">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw"> <Form.Item label={t('jobs.fields.rate_ma3s')} name="rate_ma3s">
<CurrencyInput min={0} disabled={jobRO}/> <CurrencyInput min={0} disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s"> {
<CurrencyInput min={0} disabled={jobRO}/> // <Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
</Form.Item> // <CurrencyInput min={0}disabled={jobRO} />
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s"> // </Form.Item>
<CurrencyInput min={0} disabled={jobRO}/> // <Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
</Form.Item> // <CurrencyInput min={0}disabled={jobRO} />
{ // </Form.Item>
// <Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl"> }
// <CurrencyInput min={0}disabled={jobRO} /> <Form.Item label={t('jobs.fields.rate_matd')} name="rate_matd">
// </Form.Item> <CurrencyInput min={0} disabled={jobRO} />
// <Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs"> </Form.Item>
// <CurrencyInput min={0}disabled={jobRO} /> </FormRow>
// </Form.Item> {InstanceRenderManager({
} imex: <JobsDetailRatesParts form={form} />,
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd"> rome: (
<CurrencyInput min={0} disabled={jobRO}/> <>
</Form.Item> <Divider orientation="left">Tax Profile</Divider>
</FormRow> <JobsDetailRatesProfileOVerride form={form} />
{ <JobsDetailRatesParts form={form} />
InstanceRenderManager({rome: <> <JobsDetailRatesLabor form={form} />
<Divider orientation="left">Tax Profile</Divider> <JobsDetailRatesMaterials form={form} />
<JobsDetailRatesProfileOVerride form={form}/> <JobsDetailRatesOther form={form} />
<JobsDetailRatesParts form={form}/> <JobsDetailRatesTaxes form={form} />
<JobsDetailRatesLabor form={form}/> </>
<JobsDetailRatesMaterials form={form}/> ),
<JobsDetailRatesOther form={form}/> promanager: "USE_ROME"
<JobsDetailRatesTaxes form={form}/> })}
</div>
</>}) );
}
</div>
);
} }
export default connect(mapStateToProps, null)(JobsDetailRates); export default connect(mapStateToProps, null)(JobsDetailRates);

View File

@@ -34,19 +34,20 @@ export function PartDispatchTableComponent({
// const selectedBill = search.billid; // const selectedBill = search.billid;
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const Templates = TemplateList("job_special"); const Templates = TemplateList("job_special", job);
const {refetch} = billsQuery; const {refetch} = billsQuery;
const recordActions = (record) => ( const recordActions = (record) => (
<Space wrap> <Space wrap>
<PrintWrapperComponent <PrintWrapperComponent
templateObject={{ templateObject={{
name: Templates.parts_dispatch.key, name: Templates.parts_dispatch.key,
variables: {id: record.id}, variables: { id: record.id },
}} }}
/> messageObject={{ subject: Templates.parts_dispatch.subject }}
</Space> />
</Space>
); );
const columns = [ const columns = [
{ {

View File

@@ -7,6 +7,7 @@ import {connect} from "react-redux";
import {createStructuredSelector} from "reselect"; import {createStructuredSelector} from "reselect";
import {selectTechnician} from "../../redux/tech/tech.selectors"; import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors"; import {selectBodyshop} from "../../redux/user/user.selectors";
import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -31,7 +32,13 @@ export function ProductionColumnsComponent({
}) { }) {
const [columns, setColumns] = columnState; const [columns, setColumns] = columnState;
const {t} = useTranslation(); const {t} = useTranslation();
const {
treatments: { Enhanced_Payroll },
} = useSplitTreatments({
attributes: {},
names: ['Enhanced_Payroll'],
splitKey: bodyshop.imexshopid,
});
const handleAdd = (e) => { const handleAdd = (e) => {
setColumns([ setColumns([
...columns, ...columns,
@@ -41,6 +48,7 @@ export function ProductionColumnsComponent({
state: tableState, state: tableState,
data, data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments:{Enhanced_Payroll}
}).filter((i) => i.key === e.key), }).filter((i) => i.key === e.key),
]); ]);
}; };
@@ -52,6 +60,7 @@ export function ProductionColumnsComponent({
state: tableState, state: tableState,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
refetch, refetch,
treatments:{Enhanced_Payroll}
}); });
const menu = { const menu = {

View File

@@ -29,7 +29,8 @@ import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.co
import {store} from "../../redux/store"; import {store} from "../../redux/store";
import {setModalContext} from "../../redux/modals/modals.actions"; import {setModalContext} from "../../redux/modals/modals.actions";
const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => { const r = ({technician, state, activeStatuses, data, bodyshop, refetch, treatments}) => {
const {Enhanced_Payroll} = treatments;
return [ return [
{ {
title: i18n.t("jobs.actions.viewdetail"), title: i18n.t("jobs.actions.viewdetail"),
@@ -42,7 +43,7 @@ const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => {
</Link> </Link>
), ),
}, },
{ ...Enhanced_Payroll.treatment === "on" ? [ {
title: i18n.t("timetickets.actions.claimtasks"), title: i18n.t("timetickets.actions.claimtasks"),
dataIndex: "claimtasks", dataIndex: "claimtasks",
key: "claimtasks", key: "claimtasks",
@@ -64,7 +65,7 @@ const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => {
{i18n.t("timetickets.actions.claimtasks")} {i18n.t("timetickets.actions.claimtasks")}
</div> </div>
), ),
}, },] : [],
{ {
title: i18n.t("jobs.fields.ro_number"), title: i18n.t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
@@ -326,7 +327,7 @@ const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => {
onFilter: (value, record) => onFilter: (value, record) =>
value.includes(record.special_coverage_policy), value.includes(record.special_coverage_policy),
render: (text, record) => ( render: (text, record) => (
<Checkbox disabled checked={record.special_coverage_policy} /> <Checkbox checked={record.special_coverage_policy} />
), ),
}, },

View File

@@ -10,6 +10,7 @@ import {UPDATE_SHOP} from "../../graphql/bodyshop.queries";
import {selectTechnician} from "../../redux/tech/tech.selectors"; import {selectTechnician} from "../../redux/tech/tech.selectors";
import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors"; import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import ProductionListColumns from "../production-list-columns/production-list-columns.data"; import ProductionListColumns from "../production-list-columns/production-list-columns.data";
import {useSplitTreatments} from '@splitsoftware/splitio-react';
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -31,6 +32,12 @@ export function ProductionListTable({
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW); const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
const [updateShop] = useMutation(UPDATE_SHOP); const [updateShop] = useMutation(UPDATE_SHOP);
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const handleSelect = async (value, option) => { const handleSelect = async (value, option) => {
setColumns( setColumns(
bodyshop.production_config bodyshop.production_config
@@ -44,6 +51,7 @@ export function ProductionListTable({
state, state,
data: data, data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments:{Enhanced_Payroll}
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width, width: k.width,
}; };
@@ -100,6 +108,7 @@ export function ProductionListTable({
refetch, refetch,
data: data, data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: {Enhanced_Payroll}
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width, width: k.width,
}; };

View File

@@ -28,13 +28,12 @@ export function ProductionListTable({loading, data, refetch, bodyshop, technicia
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const {treatments: {Production_List_Status_Colors}} = useSplitTreatments({ const {treatments: {Production_List_Status_Colors, Enhanced_Payroll}} = useSplitTreatments({
attributes: {}, attributes: {},
names: ["Production_List_Status_Colors"], names: ["Production_List_Status_Colors","Enhanced_Payroll"],
splitKey: bodyshop.imexshopid, splitKey: bodyshop.imexshopid,
}); });
const assoc = bodyshop.associations.find( const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email (a) => a.useremail === currentUser.email
); );
@@ -69,6 +68,7 @@ export function ProductionListTable({loading, data, refetch, bodyshop, technicia
state, state,
data, data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: {Production_List_Status_Colors, Enhanced_Payroll}
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width ?? 100, width: k.width ?? 100,
}; };
@@ -89,6 +89,7 @@ export function ProductionListTable({loading, data, refetch, bodyshop, technicia
state, state,
data: data, data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: {Production_List_Status_Colors, Enhanced_Payroll}
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width ?? 100, width: k.width ?? 100,
}; };

View File

@@ -141,9 +141,10 @@ export function ReportCenterModalComponent({reportCenterModal, bodyshop}) {
const grouped = _.groupBy(FilteredReportsList, "group"); const grouped = _.groupBy(FilteredReportsList, "group");
const groupExcludeKeyFilter = [...!HasFeatureAccess({featureName: 'bills'})? ["purchases"]:[], const groupExcludeKeyFilter = [
...!HasFeatureAccess({featureName: 'timetickets'})? ["payroll"]:[], ...(!HasFeatureAccess({ featureName: 'bills', bodyshop }) ? ['purchases'] : []),
] ...(!HasFeatureAccess({ featureName: 'timetickets', bodyshop }) ? ['payroll'] : []),
];
return ( return (
<div> <div>

View File

@@ -18,7 +18,6 @@ import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
import {useLocation, useNavigate} from "react-router-dom"; import {useLocation, useNavigate} from "react-router-dom";
import ShopInfoTaskPresets from "./shop-info.task-presets.component"; import ShopInfoTaskPresets from "./shop-info.task-presets.component";
import queryString from "query-string"; import queryString from "query-string";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({

View File

@@ -241,17 +241,17 @@ export function ShopInfoResponsibilityCenterComponent({bodyshop, form}) {
</Select> </Select>
</Form.Item> </Form.Item>
<Space align="center"> <Space align="center">
d <DeleteFilled
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);
}} }}
/> />
<FormListMoveArrows <FormListMoveArrows
move={move} move={move}
index={index} index={index}
total={fields.length} total={fields.length}
/> />
</Space> </Space>
</LayoutFormRow> </LayoutFormRow>
</Form.Item> </Form.Item>

View File

@@ -8,6 +8,7 @@ import {Link, useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect"; import {createStructuredSelector} from "reselect";
import RomeLogo from "../../assets/RomeOnlineBlue.png"; import RomeLogo from "../../assets/RomeOnlineBlue.png";
import ImEXOnlineLogo from "../../assets/logo192.png"; import ImEXOnlineLogo from "../../assets/logo192.png";
import ProManagerLogo from '../../assets/promanager/ProManagerLogo.gif';
import InstanceRenderManager from '../../utils/instanceRenderMgr'; import InstanceRenderManager from '../../utils/instanceRenderMgr';
import {emailSignInStart, sendPasswordReset,} from "../../redux/user/user.actions"; import {emailSignInStart, sendPasswordReset,} from "../../redux/user/user.actions";
import {selectCurrentUser, selectLoginLoading, selectSignInError,} from "../../redux/user/user.selectors"; import {selectCurrentUser, selectLoginLoading, selectSignInError,} from "../../redux/user/user.selectors";
@@ -53,9 +54,9 @@ export function SignInComponent({
return ( return (
<div className="login-container"> <div className="login-container">
<div className="login-logo-container"> <div className="login-logo-container">
<img src={InstanceRenderManager({imex:ImEXOnlineLogo, rome:RomeLogo, promanager:'https://www.web-est.com/img/web_est_logo_software.gif'})} width={200} alt={InstanceRenderManager({imex:t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")})}/> <img src={InstanceRenderManager({imex:ImEXOnlineLogo, rome:RomeLogo, promanager:ProManagerLogo})} width={InstanceRenderManager({imex:200, rome:200,promanager:450})} alt={InstanceRenderManager({imex:t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")})}/>
<Typography.Title>{ <Typography.Title>{
InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:t("titles.promanager")}) InstanceRenderManager({imex: t("titles.imexonline"), rome: t("titles.romeonline"), promanager:null})
}</Typography.Title> }</Typography.Title>
</div> </div>
<Form onFinish={handleFinish} form={form} size="large"> <Form onFinish={handleFinish} form={form} size="large">

View File

@@ -65,14 +65,19 @@ export function TimeTicketList({
}, [timetickets]); }, [timetickets]);
const columns = [ const columns = [
{ ...(Enhanced_Payroll.treatment === "on"
title: t("timetickets.fields.committed"), ? [{
dataIndex: "committed_at", title: t("timetickets.fields.committed"),
key: "committed_at", dataIndex: "committed_at",
render: (text, record) => ( key: "committed_at",
<Checkbox disabled checked={record.committed_at}/> render: (text, record) => (
), <Checkbox disabled checked={record.committed_at}/>
}, ),
},]
: [
]),
{ {
title: t("timetickets.fields.date"), title: t("timetickets.fields.date"),
dataIndex: "date", dataIndex: "date",

View File

@@ -16,7 +16,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component"; import {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketList from "../time-ticket-list/time-ticket-list.component"; import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
import TimeTicketCalculatorComponent from "../time-ticket-calculator/time-ticket-calculator.component";
import {useSplitTreatments} from "@splitsoftware/splitio-react"; import {useSplitTreatments} from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({

View File

@@ -178,7 +178,7 @@ export function TimeTicketModalContainer({
onCancel={handleCancel} onCancel={handleCancel}
afterClose={() => form.resetFields()} afterClose={() => form.resetFields()}
footer={ footer={
<span> <Space>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button> <Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
<Button <Button
loading={loading} loading={loading}
@@ -198,7 +198,7 @@ export function TimeTicketModalContainer({
{t("general.actions.saveandnew")} {t("general.actions.saveandnew")}
</Button> </Button>
)} )}
</span> </Space>
} }
destroyOnClose destroyOnClose
> >
@@ -229,9 +229,12 @@ export function TimeTicketModalContainer({
<PageHeader <PageHeader
extra={ extra={
<Space> <Space>
{
Enhanced_Payroll.treatment === 'on' &&
<TimeTicketsCommitToggleComponent <TimeTicketsCommitToggleComponent
timeticket={timeTicketModal.context?.timeticket} timeticket={timeTicketModal.context?.timeticket}
/> />
}
<Button onClick={handleCancel}> <Button onClick={handleCancel}>
{t("general.actions.cancel")} {t("general.actions.cancel")}
</Button> </Button>

View File

@@ -1,92 +1,91 @@
import {HeartOutlined} from "@ant-design/icons"; import { HeartOutlined } from '@ant-design/icons';
import {Select, Space, Tag} from "antd"; import { Select, Space, Tag } from 'antd';
import React, {forwardRef, useEffect, useState} from "react"; import React, { forwardRef, useEffect, useState } from 'react';
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from '../../utils/PhoneFormatter';
const {Option} = Select; const { Option } = Select;
//To be used as a form element only. //To be used as a form element only.
const VendorSearchSelect = ( const VendorSearchSelect = (
{value, onChange, options, onSelect, disabled, preferredMake, showPhone}, { value, onChange, options, onSelect, disabled, preferredMake, showPhone },
ref ref
) => { ) => {
const [option, setOption] = useState(value); const [option, setOption] = useState(value);
useEffect(() => { useEffect(() => {
if (value !== option && onChange) { if (value !== option && onChange) {
onChange(option); onChange(option);
} }
}, [value, option, onChange]); }, [value, option, onChange]);
const favorites = const favorites =
preferredMake && options preferredMake && options
? options.filter( ? options.filter(
(o) => (o) =>
o.favorite.filter( o.favorite.filter((f) => f.toLowerCase() === preferredMake.toLowerCase()).length > 0
(f) => f.toLowerCase() === preferredMake.toLowerCase() )
).length > 0 : [];
)
: [];
return ( return (
<Select <Select
ref={ref} ref={ref}
showSearch showSearch
value={option} value={option}
style={{ style={{
width: "100%", width: '100%',
}} }}
popupMatchSelectWidth={false} labelRender={({ label, value, ...rest }) => {
onChange={setOption} if (!value || !options) return label;
optionFilterProp="name" const discount = options?.find((o) => o.id === value)?.discount;
onSelect={onSelect} return (
disabled={disabled || false} <div className="imex-flex-row" style={{ width: '100%' }}>
optionLabelProp={"name"} <div style={{ flex: 1 }}>{label}</div>
>
{favorites
? favorites.map((o) => (
<Option
key={`favorite-${o.id}`}
value={o.id}
name={o.name}
discount={o.discount}
>
<div className="imex-flex-row">
<div style={{flex: 1}}>{o.name}</div>
<Space style={{marginLeft: "1rem"}}>
<HeartOutlined style={{color: "red"}}/>
{o.phone && showPhone && (
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>
)}
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))
: null}
{options
? options.map((o) => (
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
<div className="imex-flex-row" style={{width: "100%"}}>
<div style={{flex: 1}}>{o.name}</div>
<Space style={{marginLeft: "1rem"}}> {discount && discount !== 0 ? <Tag color="green">{`${discount * 100}%`}</Tag> : null}
{o.phone && showPhone && ( </div>
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter> );
)} }}
{o.discount && o.discount !== 0 ? ( popupMatchSelectWidth={false}
<Tag color="green">{`${o.discount * 100}%`}</Tag> onChange={setOption}
) : null} optionFilterProp="name"
</Space> onSelect={onSelect}
</div> disabled={disabled || false}
</Option> optionLabelProp={'name'}
>
{favorites
? favorites.map((o) => (
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
<div className="imex-flex-row">
<div style={{ flex: 1 }}>{o.name}</div>
<Space style={{ marginLeft: '1rem' }}>
<HeartOutlined style={{ color: 'red' }} />
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))
: null}
{options
? options.map((o) => (
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
<div className="imex-flex-row" style={{ width: '100%' }}>
<div style={{ flex: 1 }}>{o.name}</div>
)) <Space style={{ marginLeft: '1rem' }}>
: null} {o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
</Select> {o.discount && o.discount !== 0 ? (
); <Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))
: null}
</Select>
);
}; };
export default forwardRef(VendorSearchSelect); export default forwardRef(VendorSearchSelect);

View File

@@ -126,7 +126,7 @@ export function BillsListPage({
state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order, state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Checkbox disabled checked={record.is_credit_memo}/> <Checkbox checked={record.is_credit_memo}/>
), ),
}, },
{ {
@@ -136,7 +136,7 @@ export function BillsListPage({
sorter: (a, b) => a.exported - b.exported, sorter: (a, b) => a.exported - b.exported,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox disabled checked={record.exported}/>, render: (text, record) => <Checkbox checked={record.exported}/>,
}, },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),

View File

@@ -41,8 +41,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO( export const socket = SocketIO(
import.meta.env.PROD import.meta.env.PROD
? import.meta.env.VITE_APP_AXIOS_BASE_API_URL ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL
: window.location.origin, : "http://localhost:4000", // for dev testing,
// "http://localhost:4000", // for dev testing,
{ {
path: "/ws", path: "/ws",
withCredentials: true, withCredentials: true,

View File

@@ -162,26 +162,24 @@ sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
{ text: "False", value: false }, { text: "False", value: false },
], ],
onFilter: (value, record) => record.successful === value, onFilter: (value, record) => record.successful === value,
render: (text, record) => ( render: (text, record) => <Checkbox checked={record.successful} />,
<Checkbox disabled checked={record.successful}/> },
), {
}, title: t("general.labels.message"),
{ dataIndex: "message",
title: t("general.labels.message"), key: "message",
dataIndex: "message", render: (text, record) =>
key: "message", record.message && (
render: (text, record) => <div>
record.message && ( <ul>
<div> {JSON.parse(record.message).map((m, idx) => (
<ul> <li key={idx}>{m}</li>
{JSON.parse(record.message).map((m, idx) => ( ))}
<li key={idx}>{m}</li> </ul>
))} </div>
</ul> ),
</div> },
), ];
},
];
return ( return (
<Card <Card

View File

@@ -372,8 +372,9 @@ export function JobsCloseComponent({job, bodyshop, jobRO, insertAuditTrail}) {
</Form.Item> </Form.Item>
)} )}
</LayoutFormRow> </LayoutFormRow>
<Divider>{t("jobs.labels.multipayers")}</Divider>
{Qb_Multi_Ar.treatment === "on" && ( {Qb_Multi_Ar.treatment === "on" && (
<><Divider>{t("jobs.labels.multipayers")}</Divider>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col lg={8} md={24}> <Col lg={8} md={24}>
<Form.List <Form.List
@@ -452,7 +453,9 @@ export function JobsCloseComponent({job, bodyshop, jobRO, insertAuditTrail}) {
<DeleteFilled <DeleteFilled
disabled={jobRO} disabled={jobRO}
onClick={() => { onClick={() => {
if(!jobRO){
remove(field.name); remove(field.name);
}
}} }}
/> />
</Space> </Space>
@@ -530,6 +533,7 @@ export function JobsCloseComponent({job, bodyshop, jobRO, insertAuditTrail}) {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>
</>
)} )}
<Divider/> <Divider/>
<JobsCloseLines job={job}/> <JobsCloseLines job={job}/>

View File

@@ -332,7 +332,7 @@ export function JobsDetailPage({
{ {
key: "partssublet", key: "partssublet",
icon: <ToolFilled/>, icon: <ToolFilled/>,
label: t("menus.jobsdetail.partssublet"), label: HasFeatureAccess({featureName: "bills", bodyshop}) ? t("menus.jobsdetail.partssublet") : t("menus.jobsdetail.parts"),
children: <JobsDetailPliContainer job={job}/>, children: <JobsDetailPliContainer job={job}/>,
}, },
...InstanceRenderManager({ imex: true, rome: true, promanager: HasFeatureAccess({ featureName: 'timetickets', bodyshop }) }) ? [ { ...InstanceRenderManager({ imex: true, rome: true, promanager: HasFeatureAccess({ featureName: 'timetickets', bodyshop }) }) ? [ {

View File

@@ -521,13 +521,12 @@ export function Manage({conflict, bodyshop,enableJoyRide,joyRideSteps,setJoyRide
}} }}
> >
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
{`Joy Ride Status: ${enableJoyRide}`}
<div> <div>
{`${InstanceRenderManager({ {`${InstanceRenderManager({
imex: t('titles.imexonline'), imex: t('titles.imexonline'),
rome: t('titles.romeonline'), rome: t('titles.romeonline'),
promanager: t('titles.promanager'), promanager: t('titles.promanager'),
})} ${import.meta.env.VITE_APP_GIT_SHA || 'Local Build'} - ${ })} - ${
import.meta.env.VITE_APP_GIT_SHA_DATE import.meta.env.VITE_APP_GIT_SHA_DATE
}`} }`}
</div> </div>

View File

@@ -72,7 +72,7 @@ export function ShopPage({bodyshop, setSelectedHeader, setBreadcrumbs}) {
}, },
); );
if(HasFeatureAccess("csi")){ if(HasFeatureAccess({featureName:"csi", bodyshop})){
items.push({ items.push({
key: "csiq", key: "csiq",
label: t("bodyshop.labels.csiq"), label: t("bodyshop.labels.csiq"),

View File

@@ -1,103 +1,110 @@
import {useQuery} from "@apollo/client"; import { useQuery } from '@apollo/client';
import {Col, Row, Space} from "antd"; import { Col, Row, Space } from 'antd';
import dayjs from "../../utils/day"; import dayjs from '../../utils/day';
import React, {useEffect} from "react"; import React, { useEffect } from 'react';
import {useTranslation} from "react-i18next"; import { useTranslation } from 'react-i18next';
import {connect} from "react-redux"; import { connect } from 'react-redux';
import {useSearchParams} from "react-router-dom"; import { useSearchParams } from 'react-router-dom';
import {createStructuredSelector} from "reselect"; import { createStructuredSelector } from 'reselect';
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from '../../components/alert/alert.component';
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from '../../components/rbac-wrapper/rbac-wrapper.component';
import TimeTicketsDatesSelector import TimeTicketsDatesSelector from '../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component';
from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component"; import TimeTicketList from '../../components/time-ticket-list/time-ticket-list.component';
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component"; import TimeTicketsPayrollTable from '../../components/time-tickets-payroll-table/time-tickets-payroll-table.component';
import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component"; import TimeTicketsSummaryEmployees from '../../components/time-tickets-summary-employees/time-tickets-summary-employees.component';
import TimeTicketsSummaryEmployees import { QUERY_TIME_TICKETS_IN_RANGE } from '../../graphql/timetickets.queries';
from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component"; import TimeTicketsAttendanceTable from '../../components/time-tickets-attendance-table/time-tickets-attendance-table.component';
import {QUERY_TIME_TICKETS_IN_RANGE} from "../../graphql/timetickets.queries"; import { setBreadcrumbs, setSelectedHeader } from '../../redux/application/application.actions';
import TimeTicketsAttendanceTable import TimeTicketsCommit from '../../components/time-tickets-commit/time-tickets-commit.component';
from "../../components/time-tickets-attendance-table/time-tickets-attendance-table.component"; import FeatureWrapperComponent from '../../components/feature-wrapper/feature-wrapper.component';
import {setBreadcrumbs, setSelectedHeader,} from "../../redux/application/application.actions";
import TimeTicketsCommit from "../../components/time-tickets-commit/time-tickets-commit.component";
import FeatureWrapperComponent from "../../components/feature-wrapper/feature-wrapper.component";
import InstanceRenderManager from '../../utils/instanceRenderMgr'; import InstanceRenderManager from '../../utils/instanceRenderMgr';
import { useSplitTreatments } from '@splitsoftware/splitio-react';
import { selectBodyshop } from '../../redux/user/user.selectors';
const mapStateToProps = createStructuredSelector({}); const mapStateToProps = createStructuredSelector({
bodyshop:selectBodyshop
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
}); });
export function TimeTicketsContainer({ const mapDispatchToProps = (dispatch) => ({
bodyshop, setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setBreadcrumbs, setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setSelectedHeader, });
}) {
const {t} = useTranslation();
const [searchParams] = useSearchParams();
const {start, end} = Object.fromEntries(searchParams);
const startDate = start export function TimeTicketsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
? dayjs(start) const {
: dayjs().startOf("week").subtract(7, "day"); treatments: { Enhanced_Payroll },
const endDate = end ? dayjs(end) : dayjs().endOf("week"); } = useSplitTreatments({
attributes: {},
names: ['Enhanced_Payroll'],
splitKey: bodyshop.imexshopid,
});
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const { start, end } = Object.fromEntries(searchParams);
const {loading, error, data} = useQuery(QUERY_TIME_TICKETS_IN_RANGE, { const startDate = start ? dayjs(start) : dayjs().startOf('week').subtract(7, 'day');
variables: { const endDate = end ? dayjs(end) : dayjs().endOf('week');
start: startDate,
end: endDate, const { loading, error, data } = useQuery(QUERY_TIME_TICKETS_IN_RANGE, {
}, variables: {
fetchPolicy: "network-only", start: startDate,
nextFetchPolicy: "network-only", end: endDate,
},
fetchPolicy: 'network-only',
nextFetchPolicy: 'network-only',
});
useEffect(() => {
document.title = t('titles.timetickets', {
app: InstanceRenderManager({
imex: '$t(titles.imexonline)',
rome: '$t(titles.romeonline)',
promanager: '$t(titles.promanager)',
}),
}); });
setSelectedHeader('timetickets');
setBreadcrumbs([
{
link: '/manage/timetickets',
label: t('titles.bc.timetickets'),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => { if (error) return <AlertComponent message={error.message} type="error" />;
document.title = t("titles.timetickets",{app: InstanceRenderManager({imex:'$t(titles.imexonline)', rome: '$t(titles.romeonline)', promanager: '$t(titles.promanager)'})} );
setSelectedHeader("timetickets");
setBreadcrumbs([
{
link: "/manage/timetickets",
label: t("titles.bc.timetickets"),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
if (error) return <AlertComponent message={error.message} type="error"/>; return (
<FeatureWrapperComponent featureName="timetickets">
return ( <RbacWrapper action="timetickets:list">
<FeatureWrapperComponent featureName='timetickets'> <Row gutter={[16, 16]}>
<RbacWrapper action="timetickets:list"> <Col span={24}>
<Row gutter={[16, 16]}> <TimeTicketList
<Col span={24}> loading={loading}
<TimeTicketList timetickets={data ? data.timetickets : []}
loading={loading} extra={
timetickets={data ? data.timetickets : []} <Space wrap>
extra={ <TimeTicketsAttendanceTable />
<Space wrap> <TimeTicketsPayrollTable />
<TimeTicketsAttendanceTable/> {Enhanced_Payroll.treatment === 'on' && (
<TimeTicketsPayrollTable/> <TimeTicketsCommit timetickets={data ? data.timetickets : []} />
<TimeTicketsCommit timetickets={data ? data.timetickets : []}/> )}
<TimeTicketsDatesSelector/> <TimeTicketsDatesSelector />
</Space> </Space>
} }
/> />
</Col> </Col>
<Col span={24}> <Col span={24}>
<TimeTicketsSummaryEmployees <TimeTicketsSummaryEmployees
loading={loading} loading={loading}
timetickets={data ? data.timetickets : []} timetickets={data ? data.timetickets : []}
startDate={startDate} startDate={startDate}
endDate={endDate} endDate={endDate}
/> />
</Col> </Col>
</Row> </Row>
</RbacWrapper> </RbacWrapper>
</FeatureWrapperComponent> </FeatureWrapperComponent>
); );
} }
export default connect( export default connect(mapStateToProps, mapDispatchToProps)(TimeTicketsContainer);
mapStateToProps,
mapDispatchToProps
)(TimeTicketsContainer);

View File

@@ -47,6 +47,7 @@ import client from "../../utils/GraphQLClient";
import {QUERY_EULA} from "../../graphql/bodyshop.queries"; import {QUERY_EULA} from "../../graphql/bodyshop.queries";
import day from "../../utils/day"; import day from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { Userpilot } from "userpilot";
const fpPromise = FingerprintJS.load(); const fpPromise = FingerprintJS.load();
@@ -227,6 +228,15 @@ export function* signInSuccessSaga({payload}) {
window.$crisp.push(['set', 'user:nickname', [payload.displayName || payload.email]]); window.$crisp.push(['set', 'user:nickname', [payload.displayName || payload.email]]);
window.$crisp.push(['set', 'session:segments', [['user']]]); window.$crisp.push(['set', 'session:segments', [['user']]]);
}, },
promanager: () =>{
Userpilot.identify(
payload.email,
{
email: payload.email,
}
);
console.log("*** Userpilot identified.")
}
}); });
} catch (error) { } catch (error) {

View File

@@ -749,7 +749,7 @@
"refuelcharge": "Refuel Charge (per liter/gallon)", "refuelcharge": "Refuel Charge (per liter/gallon)",
"scheduledreturn": "Scheduled Return", "scheduledreturn": "Scheduled Return",
"start": "Contract Start", "start": "Contract Start",
"statetax": "State Taxes", "statetax": "Provincial/State Taxes",
"status": "Status" "status": "Status"
}, },
"labels": { "labels": {
@@ -899,6 +899,7 @@
"refhrs": "Refinish Hrs" "refhrs": "Refinish Hrs"
}, },
"titles": { "titles": {
"joblifecycle": "Job Life Cycles",
"labhours": "Total Body Hours", "labhours": "Total Body Hours",
"larhours": "Total Refinish Hours", "larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency", "monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -1164,6 +1165,7 @@
"loadingshop": "Loading shop data...", "loadingshop": "Loading shop data...",
"loggingin": "Authorizing...", "loggingin": "Authorizing...",
"markedexported": "Manually marked as exported.", "markedexported": "Manually marked as exported.",
"media": "Media",
"message": "Message", "message": "Message",
"monday": "Monday", "monday": "Monday",
"na": "N/A", "na": "N/A",
@@ -2002,7 +2004,7 @@
"savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ", "savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ", "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ",
"specialcoveragepolicy": "Special Coverage Policy Applies", "specialcoveragepolicy": "Special Coverage Policy Applies",
"state_tax_amt": "State Taxes", "state_tax_amt": "Provincial/State Taxes",
"subletstotal": "Sublets Total", "subletstotal": "Sublets Total",
"subtotal": "Subtotal", "subtotal": "Subtotal",
"supplementnote": "The Job had a supplement imported.", "supplementnote": "The Job had a supplement imported.",
@@ -2174,6 +2176,7 @@
"insurance": "Insurance Information", "insurance": "Insurance Information",
"labor": "Labor", "labor": "Labor",
"lifecycle": "Lifecycle", "lifecycle": "Lifecycle",
"parts": "Parts",
"partssublet": "Parts & Bills", "partssublet": "Parts & Bills",
"rates": "Rates", "rates": "Rates",
"repairdata": "Repair Data", "repairdata": "Repair Data",
@@ -2558,7 +2561,7 @@
"invoice_total_payable": "Invoice (Total Payable)", "invoice_total_payable": "Invoice (Total Payable)",
"iou_form": "IOU Form", "iou_form": "IOU Form",
"job_costing_ro": "Job Costing", "job_costing_ro": "Job Costing",
"job_lifecycle_ro": "", "job_lifecycle_ro": "Job Lifecycle",
"job_notes": "Job Notes", "job_notes": "Job Notes",
"key_tag": "Key Tag", "key_tag": "Key Tag",
"labels": { "labels": {

View File

@@ -899,6 +899,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -2174,6 +2175,7 @@
"insurance": "", "insurance": "",
"labor": "Labor", "labor": "Labor",
"lifecycle": "", "lifecycle": "",
"parts": "",
"partssublet": "Piezas / Subarrendamiento", "partssublet": "Piezas / Subarrendamiento",
"rates": "", "rates": "",
"repairdata": "Datos de reparación", "repairdata": "Datos de reparación",

View File

@@ -899,6 +899,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -2174,6 +2175,7 @@
"insurance": "", "insurance": "",
"labor": "La main d'oeuvre", "labor": "La main d'oeuvre",
"lifecycle": "", "lifecycle": "",
"parts": "",
"partssublet": "Pièces / Sous-location", "partssublet": "Pièces / Sous-location",
"rates": "", "rates": "",
"repairdata": "Données de réparation", "repairdata": "Données de réparation",

View File

@@ -4,6 +4,7 @@ import { initReactI18next } from 'react-i18next';
import en_Translation from './en_us/common.json'; import en_Translation from './en_us/common.json';
import es_Translation from './es/common.json'; import es_Translation from './es/common.json';
import fr_Translation from './fr/common.json'; import fr_Translation from './fr/common.json';
import { GenerateTemplates } from '../utils/RenderTemplate';
// the translations // the translations
// (tip move them in a JSON file and import them) // (tip move them in a JSON file and import them)
@@ -13,19 +14,27 @@ const resources = {
'es-MX': es_Translation, 'es-MX': es_Translation,
}; };
i18n i18n
.use(initReactI18next)
.use(LanguageDetector) // passes i18n down to react-i18next .use(LanguageDetector) // passes i18n down to react-i18next
.init({ .use(initReactI18next)
resources, .init(
//lng: "en", {
detection: {}, resources,
fallbackLng: 'en-US', //lng: "en",
debug: import.meta.env.DEV, detection: {},
//keySeparator: false, // we do not use keys in form messages.welcome fallbackLng: 'en-US',
interpolation: { debug: import.meta.env.DEV,
escapeValue: false, // react already safes from xss react: {
skipOnVariables: false, useSuspense: true,
},
//keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
escapeValue: false, // react already safes from xss
skipOnVariables: false,
},
}, },
}); (error) => {
GenerateTemplates();
}
);
export default i18n; export default i18n;

View File

@@ -14,7 +14,12 @@ const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server; jsreport.serverUrl = server;
const Templates = TemplateList(); let Templates;
export function GenerateTemplates(){
//Required as a part of the transition to Vite.
//Previous method had the template hash generating before translations loaded, resulting in empty files.
Templates = TemplateList()
}
export default async function RenderTemplate( export default async function RenderTemplate(
templateObject, templateObject,

View File

@@ -5,7 +5,7 @@ import * as path from 'path';
import * as url from 'url'; import * as url from 'url';
import { defineConfig } from 'vite'; import { defineConfig } from 'vite';
import { ViteEjsPlugin } from 'vite-plugin-ejs'; import { ViteEjsPlugin } from 'vite-plugin-ejs';
import CompressionPlugin from 'vite-plugin-compression'; //import CompressionPlugin from 'vite-plugin-compression';
import { VitePWA } from 'vite-plugin-pwa'; import { VitePWA } from 'vite-plugin-pwa';
import InstanceRenderManager from './src/utils/instanceRenderMgr'; import InstanceRenderManager from './src/utils/instanceRenderMgr';
@@ -103,8 +103,11 @@ export default defineConfig({
}), }),
reactVirtualized(), reactVirtualized(),
react(), react(),
CompressionPlugin(), // CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
], ],
define:{
"APP_VERSION": JSON.stringify(process.env.npm_package_version)
},
server: { server: {
host: true, host: true,
port: 3000, port: 3000,

View File

@@ -569,6 +569,13 @@
table: table:
name: parts_orders name: parts_orders
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: billid
table:
name: tasks
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -818,6 +825,13 @@
table: table:
name: inventory name: inventory
schema: public schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: ioevents
schema: public
- name: jobs - name: jobs
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -846,6 +860,13 @@
table: table:
name: phonebook name: phonebook
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: tasks
schema: public
- name: timetickets - name: timetickets
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -2675,6 +2696,13 @@
- table: - table:
name: ioevents name: ioevents
schema: public schema: public
object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: user
using:
foreign_key_constraint_on: useremail
- table: - table:
name: job_ar_schema name: job_ar_schema
schema: public schema: public
@@ -2824,6 +2852,13 @@
table: table:
name: parts_order_lines name: parts_order_lines
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: joblineid
table:
name: tasks
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -3311,6 +3346,13 @@
table: table:
name: scoreboard name: scoreboard
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: jobid
table:
name: tasks
schema: public
- name: timetickets - name: timetickets
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -5008,6 +5050,13 @@
table: table:
name: parts_order_lines name: parts_order_lines
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: partsorderid
table:
name: tasks
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -5623,6 +5672,128 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
- table:
name: tasks
schema: public
object_relationships:
- name: bill
using:
foreign_key_constraint_on: billid
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: job
using:
foreign_key_constraint_on: jobid
- name: jobline
using:
foreign_key_constraint_on: joblineid
- name: parts_order
using:
foreign_key_constraint_on: partsorderid
- name: user
using:
foreign_key_constraint_on: assigned_to
- name: userByCreatedBy
using:
foreign_key_constraint_on: created_by
insert_permissions:
- role: user
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
select_permissions:
- role: user
permission:
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
update_permissions:
- role: user
permission:
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table: - table:
name: timetickets name: timetickets
schema: public schema: public
@@ -6006,6 +6177,13 @@
table: table:
name: exportlog name: exportlog
schema: public schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: useremail
table:
name: ioevents
schema: public
- name: messages - name: messages
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -6034,6 +6212,20 @@
table: table:
name: parts_orders name: parts_orders
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: assigned_to
table:
name: tasks
schema: public
- name: tasksByCreatedBy
using:
foreign_key_constraint_on:
column: created_by
table:
name: tasks
schema: public
- name: timetickets - name: timetickets
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:

View File

@@ -0,0 +1 @@
DROP TABLE "public"."tasks";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."tasks" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "title" text NOT NULL, "description" Text, "deleted" boolean NOT NULL DEFAULT false, "deleted_at" timestamptz, "due_date" timestamptz, "created_by" text NOT NULL, "assigned_to" Text, "completed" boolean NOT NULL DEFAULT false, "completed_at" timestamptz, "remind_at" timestamptz, "priority" numeric, "bodyshopid" UUID NOT NULL, "jobid" UUID NOT NULL, "joblineid" UUID, "partsorderid" UUID, "billid" UUID, PRIMARY KEY ("id") , FOREIGN KEY ("created_by") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("assigned_to") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE set null ON DELETE set null, FOREIGN KEY ("partsorderid") REFERENCES "public"."parts_orders"("id") ON UPDATE set null ON DELETE set null, FOREIGN KEY ("billid") REFERENCES "public"."bills"("id") ON UPDATE set null ON DELETE set null);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_tasks_updated_at"
BEFORE UPDATE ON "public"."tasks"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_tasks_updated_at" ON "public"."tasks"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;

View File

@@ -16,7 +16,7 @@ const CalculateAllocations =
const CdkBase = require("../../web-sockets/web-socket"); const CdkBase = require("../../web-sockets/web-socket");
const moment = require("moment-timezone"); const moment = require("moment-timezone");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const { default: InstanceManager } = require("../../utils/instanceMgr"); const { default: InstanceManager } = require("../../utils/instanceMgr").default;
const axios = AxiosLib.create(); const axios = AxiosLib.create();
axios.interceptors.request.use((x) => { axios.interceptors.request.use((x) => {

View File

@@ -605,9 +605,9 @@ exports.default = function ({
const state_tax = Dinero(job_totals.totals.state_tax); const state_tax = Dinero(job_totals.totals.state_tax);
const local_tax = Dinero(job_totals.totals.local_tax); const local_tax = Dinero(job_totals.totals.local_tax);
const RulesetToUse = InstanceManager({imex:"CANADA",rome: "US"}) const RulesetToUse = InstanceManager({ imex: 'CANADA', rome: 'US', promanager: 'US' });
if(RulesetToUse = "CANADA"){ if(RulesetToUse === "CANADA"){
if (federal_tax.getAmount() > 0) { if (federal_tax.getAmount() > 0) {
if (qbo) { if (qbo) {
// do qbo // do qbo

View File

@@ -7,7 +7,7 @@ const builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils"); const QbXmlUtils = require("./qbxml-utils");
const moment = require("moment-timezone"); const moment = require("moment-timezone");
const logger = require('../../utils/logger'); const logger = require('../../utils/logger');
const InstanceManager = require("../../utils/instanceMgr"); const InstanceManager = require("../../utils/instanceMgr").default;
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(

View File

@@ -7,7 +7,7 @@ const builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils"); const QbXmlUtils = require("./qbxml-utils");
const CreateInvoiceLines = require("../qb-receivables-lines").default; const CreateInvoiceLines = require("../qb-receivables-lines").default;
const logger = require('../../utils/logger'); const logger = require('../../utils/logger');
const InstanceManager = require('../../utils/instanceMgr'); const InstanceManager = require('../../utils/instanceMgr').default;
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(

View File

@@ -13,7 +13,7 @@ const CdkBase = require("../web-sockets/web-socket");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const _ = require("lodash"); const _ = require("lodash");
const {DiscountNotAlreadyCounted} = require("../job/job-totals"); const {DiscountNotAlreadyCounted} = require("../job/job-totals");
const InstanceManager = require('../utils/instanceMgr'); const InstanceManager = require('../utils/instanceMgr').default;
exports.default = async function (socket, jobid) { exports.default = async function (socket, jobid) {
try { try {
@@ -26,7 +26,11 @@ exports.default = async function (socket, jobid) {
const {bodyshop} = job; const {bodyshop} = job;
const taxAllocations = const taxAllocations =
InstanceManager({imex: { InstanceManager({
executeFunction:true,
deubg:true,
args: [],
imex: () => ({
local: { local: {
center: bodyshop.md_responsibility_centers.taxes.local.name, center: bodyshop.md_responsibility_centers.taxes.local.name,
sale: Dinero(job.job_totals.totals.local_tax), sale: Dinero(job.job_totals.totals.local_tax),
@@ -48,7 +52,7 @@ exports.default = async function (socket, jobid) {
profitCenter: bodyshop.md_responsibility_centers.taxes.federal, profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
costCenter: bodyshop.md_responsibility_centers.taxes.federal, costCenter: bodyshop.md_responsibility_centers.taxes.federal,
}, },
}, rome:{ }), rome: () => ({
tax_ty1: { tax_ty1: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name, center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]), sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]),
@@ -84,7 +88,7 @@ exports.default = async function (socket, jobid) {
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`], costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
}, },
} }) }) })
//Determine if there are MAPA and MASH lines already on the estimate. //Determine if there are MAPA and MASH lines already on the estimate.
@@ -439,6 +443,7 @@ if(InstanceManager({rome:true})){
}), }),
]; ];
} catch (error) { } catch (error) {
console.log(error)
CdkBase.createLogEvent( CdkBase.createLogEvent(
socket, socket,
"ERROR", "ERROR",

View File

@@ -12,6 +12,7 @@ const CdkBase = require("../web-sockets/web-socket");
const CdkWsdl = require("./cdk-wsdl").default; const CdkWsdl = require("./cdk-wsdl").default;
const {CDK_CREDENTIALS, CheckCdkResponseForError} = require("./cdk-wsdl"); const {CDK_CREDENTIALS, CheckCdkResponseForError} = require("./cdk-wsdl");
const CalcualteAllocations = require("./cdk-calculate-allocations").default; const CalcualteAllocations = require("./cdk-calculate-allocations").default;
const InstanceMgr = require("../utils/instanceMgr").default;
const moment = require("moment-timezone"); const moment = require("moment-timezone");
@@ -599,12 +600,11 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
country: country:
socket.JobData.ownr_ctry && socket.JobData.ownr_ctry &&
socket.JobData.ownr_ctry.replace(replaceSpecialRegex, ""), socket.JobData.ownr_ctry.replace(replaceSpecialRegex, ""),
postalCode: postalCode: InstanceMgr({imex: socket.JobData.ownr_zip &&
socket.JobData.ownr_zip && socket.JobData.ownr_zip //TODO Need to remove for US Based customers.
socket.JobData.ownr_zip //TODO Need to remove for US Based customers. .toUpperCase()
.toUpperCase() .replace(/\W/g, "")
.replace(/\W/g, "") .replace(/(...)/, "$1 "), rome: socket.JobData.ownr_zip }),
.replace(/(...)/, "$1 "),
stateOrProvince: stateOrProvince:
socket.JobData.ownr_st && socket.JobData.ownr_st &&
socket.JobData.ownr_st.replace(replaceSpecialRegex, ""), socket.JobData.ownr_st.replace(replaceSpecialRegex, ""),

View File

@@ -11,7 +11,7 @@ const queries = require("../graphql-client/queries");
const {phone} = require("phone"); const {phone} = require("phone");
const {admin} = require("../firebase/firebase-handler"); const {admin} = require("../firebase/firebase-handler");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr"); const InstanceManager = require("../utils/instanceMgr").default;
exports.receive = async (req, res) => { exports.receive = async (req, res) => {
//Perform request validation //Perform request validation

View File

@@ -8,7 +8,7 @@
* @property { string | object | function } imex Return this prop if Rome. * @property { string | object | function } imex Return this prop if Rome.
*/ */
function InstanceManager({ instance, debug, executeFunction, rome, promanager, imex }) { function InstanceManager({ args, instance, debug, executeFunction, rome, promanager, imex }) {
let propToReturn = null; let propToReturn = null;
switch (instance || process.env.INSTANCE) { switch (instance || process.env.INSTANCE) {