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

View File

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

View File

@@ -27,6 +27,8 @@ import "./App.styles.scss";
import handleBeta from "../utils/betaHandler";
import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr";
import { ProductFruits } from 'react-product-fruits';
const ResetPassword = lazy(() =>
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
return (
<Suspense
fallback={
<LoadingSpinner
@@ -160,6 +163,12 @@ export function App({
/>
}
>
<ProductFruits //workspaceCode="aoJoEifvezYI0Z0P"
language="en" user={{
email: currentUser.email,
username: currentUser.email,
}} />
<Routes>
<Route
path="*"

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,5 +1,5 @@
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 queryString from "query-string";
import React, {useState} from "react";
@@ -203,7 +203,7 @@ export function BillDetailEditcontainer({setPartsOrderContext, insertAuditTrail,
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported}/>
<Divider orientation="left">{t("general.labels.media")}</Divider>
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{id: data ? data.bills_by_pk.jobid : null}}

View File

@@ -173,7 +173,11 @@ export function BillDetailEditReturn({
</Form>
</Modal>
<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={() => {
setOpen(true);
}}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,7 @@ import {useTranslation} from "react-i18next";
import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./job-bills-total.styles.scss";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function JobBillsTotalComponent({
loading,
@@ -89,7 +90,7 @@ export default function JobBillsTotalComponent({
.add(Dinero(totals.parts.sublets.total))
.add(Dinero(totals.additional.shipping))
.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);

View File

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

View File

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

View File

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

View File

@@ -82,7 +82,7 @@ export default function JobReconciliationBillsTable({
state.sortedInfo.order,
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 {createStructuredSelector} from "reselect";
import {selectBodyshop} from "../../redux/user/user.selectors";
import InstanceRenderManager from '../../utils/instanceRenderMgr';
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -29,108 +30,130 @@ export function JobTotalsTableTotals({bodyshop, job}) {
total: job.job_totals.totals.subtotal,
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"),

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,8 @@ import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.co
import {store} from "../../redux/store";
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 [
{
title: i18n.t("jobs.actions.viewdetail"),
@@ -42,7 +43,7 @@ const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => {
</Link>
),
},
{
...Enhanced_Payroll.treatment === "on" ? [ {
title: i18n.t("timetickets.actions.claimtasks"),
dataIndex: "claimtasks",
key: "claimtasks",
@@ -64,7 +65,7 @@ const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => {
{i18n.t("timetickets.actions.claimtasks")}
</div>
),
},
},] : [],
{
title: i18n.t("jobs.fields.ro_number"),
dataIndex: "ro_number",
@@ -326,7 +327,7 @@ const r = ({technician, state, activeStatuses, data, bodyshop, refetch}) => {
onFilter: (value, record) =>
value.includes(record.special_coverage_policy),
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 {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
import {useSplitTreatments} from '@splitsoftware/splitio-react';
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -31,6 +32,12 @@ export function ProductionListTable({
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
const [updateShop] = useMutation(UPDATE_SHOP);
const {treatments: {Enhanced_Payroll}} = useSplitTreatments({
attributes: {},
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid,
});
const handleSelect = async (value, option) => {
setColumns(
bodyshop.production_config
@@ -44,6 +51,7 @@ export function ProductionListTable({
state,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments:{Enhanced_Payroll}
}).find((e) => e.key === k.key),
width: k.width,
};
@@ -100,6 +108,7 @@ export function ProductionListTable({
refetch,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
treatments: {Enhanced_Payroll}
}).find((e) => e.key === k.key),
width: k.width,
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,6 +8,7 @@ import {Link, useLocation, useNavigate} from "react-router-dom";
import {createStructuredSelector} from "reselect";
import RomeLogo from "../../assets/RomeOnlineBlue.png";
import ImEXOnlineLogo from "../../assets/logo192.png";
import ProManagerLogo from '../../assets/promanager/ProManagerLogo.gif';
import InstanceRenderManager from '../../utils/instanceRenderMgr';
import {emailSignInStart, sendPasswordReset,} from "../../redux/user/user.actions";
import {selectCurrentUser, selectLoginLoading, selectSignInError,} from "../../redux/user/user.selectors";
@@ -53,9 +54,9 @@ export function SignInComponent({
return (
<div className="login-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>{
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>
</div>
<Form onFinish={handleFinish} form={form} size="large">

View File

@@ -65,14 +65,19 @@ export function TimeTicketList({
}, [timetickets]);
const columns = [
{
title: t("timetickets.fields.committed"),
dataIndex: "committed_at",
key: "committed_at",
render: (text, record) => (
<Checkbox disabled checked={record.committed_at}/>
),
},
...(Enhanced_Payroll.treatment === "on"
? [{
title: t("timetickets.fields.committed"),
dataIndex: "committed_at",
key: "committed_at",
render: (text, record) => (
<Checkbox disabled checked={record.committed_at}/>
),
},]
: [
]),
{
title: t("timetickets.fields.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 {HasRbacAccess} from "../rbac-wrapper/rbac-wrapper.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";
const mapStateToProps = createStructuredSelector({

View File

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

View File

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

View File

@@ -126,7 +126,7 @@ export function BillsListPage({
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
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,
sortOrder:
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"),

View File

@@ -41,8 +41,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO(
import.meta.env.PROD
? 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",
withCredentials: true,

View File

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

View File

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

View File

@@ -332,7 +332,7 @@ export function JobsDetailPage({
{
key: "partssublet",
icon: <ToolFilled/>,
label: t("menus.jobsdetail.partssublet"),
label: HasFeatureAccess({featureName: "bills", bodyshop}) ? t("menus.jobsdetail.partssublet") : t("menus.jobsdetail.parts"),
children: <JobsDetailPliContainer job={job}/>,
},
...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' }}>
{`Joy Ride Status: ${enableJoyRide}`}
<div>
{`${InstanceRenderManager({
imex: t('titles.imexonline'),
rome: t('titles.romeonline'),
promanager: t('titles.promanager'),
})} ${import.meta.env.VITE_APP_GIT_SHA || 'Local Build'} - ${
})} - ${
import.meta.env.VITE_APP_GIT_SHA_DATE
}`}
</div>

View File

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

View File

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

View File

@@ -47,6 +47,7 @@ import client from "../../utils/GraphQLClient";
import {QUERY_EULA} from "../../graphql/bodyshop.queries";
import day from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { Userpilot } from "userpilot";
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', 'session:segments', [['user']]]);
},
promanager: () =>{
Userpilot.identify(
payload.email,
{
email: payload.email,
}
);
console.log("*** Userpilot identified.")
}
});
} catch (error) {

View File

@@ -749,7 +749,7 @@
"refuelcharge": "Refuel Charge (per liter/gallon)",
"scheduledreturn": "Scheduled Return",
"start": "Contract Start",
"statetax": "State Taxes",
"statetax": "Provincial/State Taxes",
"status": "Status"
},
"labels": {
@@ -899,6 +899,7 @@
"refhrs": "Refinish Hrs"
},
"titles": {
"joblifecycle": "Job Life Cycles",
"labhours": "Total Body Hours",
"larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -1164,6 +1165,7 @@
"loadingshop": "Loading shop data...",
"loggingin": "Authorizing...",
"markedexported": "Manually marked as exported.",
"media": "Media",
"message": "Message",
"monday": "Monday",
"na": "N/A",
@@ -2002,7 +2004,7 @@
"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. ",
"specialcoveragepolicy": "Special Coverage Policy Applies",
"state_tax_amt": "State Taxes",
"state_tax_amt": "Provincial/State Taxes",
"subletstotal": "Sublets Total",
"subtotal": "Subtotal",
"supplementnote": "The Job had a supplement imported.",
@@ -2174,6 +2176,7 @@
"insurance": "Insurance Information",
"labor": "Labor",
"lifecycle": "Lifecycle",
"parts": "Parts",
"partssublet": "Parts & Bills",
"rates": "Rates",
"repairdata": "Repair Data",
@@ -2558,7 +2561,7 @@
"invoice_total_payable": "Invoice (Total Payable)",
"iou_form": "IOU Form",
"job_costing_ro": "Job Costing",
"job_lifecycle_ro": "",
"job_lifecycle_ro": "Job Lifecycle",
"job_notes": "Job Notes",
"key_tag": "Key Tag",
"labels": {

View File

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

View File

@@ -899,6 +899,7 @@
"refhrs": ""
},
"titles": {
"joblifecycle": "",
"labhours": "",
"larhours": "",
"monthlyemployeeefficiency": "",
@@ -2174,6 +2175,7 @@
"insurance": "",
"labor": "La main d'oeuvre",
"lifecycle": "",
"parts": "",
"partssublet": "Pièces / Sous-location",
"rates": "",
"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 es_Translation from './es/common.json';
import fr_Translation from './fr/common.json';
import { GenerateTemplates } from '../utils/RenderTemplate';
// the translations
// (tip move them in a JSON file and import them)
@@ -13,19 +14,27 @@ const resources = {
'es-MX': es_Translation,
};
i18n
.use(initReactI18next)
.use(LanguageDetector) // passes i18n down to react-i18next
.init({
resources,
//lng: "en",
detection: {},
fallbackLng: 'en-US',
debug: import.meta.env.DEV,
//keySeparator: false, // we do not use keys in form messages.welcome
interpolation: {
escapeValue: false, // react already safes from xss
skipOnVariables: false,
.use(initReactI18next)
.init(
{
resources,
//lng: "en",
detection: {},
fallbackLng: 'en-US',
debug: import.meta.env.DEV,
react: {
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;

View File

@@ -14,7 +14,12 @@ const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
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(
templateObject,

View File

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

View File

@@ -569,6 +569,13 @@
table:
name: parts_orders
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: billid
table:
name: tasks
schema: public
insert_permissions:
- role: user
permission:
@@ -818,6 +825,13 @@
table:
name: inventory
schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: ioevents
schema: public
- name: jobs
using:
foreign_key_constraint_on:
@@ -846,6 +860,13 @@
table:
name: phonebook
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: tasks
schema: public
- name: timetickets
using:
foreign_key_constraint_on:
@@ -2675,6 +2696,13 @@
- table:
name: ioevents
schema: public
object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: user
using:
foreign_key_constraint_on: useremail
- table:
name: job_ar_schema
schema: public
@@ -2824,6 +2852,13 @@
table:
name: parts_order_lines
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: joblineid
table:
name: tasks
schema: public
insert_permissions:
- role: user
permission:
@@ -3311,6 +3346,13 @@
table:
name: scoreboard
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: jobid
table:
name: tasks
schema: public
- name: timetickets
using:
foreign_key_constraint_on:
@@ -5008,6 +5050,13 @@
table:
name: parts_order_lines
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: partsorderid
table:
name: tasks
schema: public
insert_permissions:
- role: user
permission:
@@ -5623,6 +5672,128 @@
_eq: X-Hasura-User-Id
- active:
_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:
name: timetickets
schema: public
@@ -6006,6 +6177,13 @@
table:
name: exportlog
schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: useremail
table:
name: ioevents
schema: public
- name: messages
using:
foreign_key_constraint_on:
@@ -6034,6 +6212,20 @@
table:
name: parts_orders
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
using:
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 moment = require("moment-timezone");
const Dinero = require("dinero.js");
const { default: InstanceManager } = require("../../utils/instanceMgr");
const { default: InstanceManager } = require("../../utils/instanceMgr").default;
const axios = AxiosLib.create();
axios.interceptors.request.use((x) => {

View File

@@ -605,9 +605,9 @@ exports.default = function ({
const state_tax = Dinero(job_totals.totals.state_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 (qbo) {
// do qbo

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
* @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;
switch (instance || process.env.INSTANCE) {