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.resetFields();
form.setFieldsValue({
...formValues,
vendorid:values.vendorid,
billlines: [],
});
form.resetFields();
// form.resetFields();
} else {
toggleModalVisible();
}

View File

@@ -1,27 +1,27 @@
import Icon, { UploadOutlined } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload, } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { MdOpenInNew } from "react-icons/md";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { CHECK_BILL_INVOICE_NUMBER } from "../../graphql/bills.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AlertComponent from "../alert/alert.component";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobSearchSelect from "../job-search-select/job-search-select.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import Icon, { UploadOutlined } from '@ant-design/icons';
import { useApolloClient } from '@apollo/client';
import { useSplitTreatments } from '@splitsoftware/splitio-react';
import { Alert, Divider, Form, Input, Select, Space, Statistic, Switch, Upload } from 'antd';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MdOpenInNew } from 'react-icons/md';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { createStructuredSelector } from 'reselect';
import { CHECK_BILL_INVOICE_NUMBER } from '../../graphql/bills.queries';
import { selectBodyshop } from '../../redux/user/user.selectors';
import dayjs from '../../utils/day';
import InstanceRenderManager from '../../utils/instanceRenderMgr';
import AlertComponent from '../alert/alert.component';
import BillFormLinesExtended from '../bill-form-lines-extended/bill-form-lines-extended.component';
import FormDatePicker from '../form-date-picker/form-date-picker.component';
import FormFieldsChanged from '../form-fields-changed-alert/form-fields-changed-alert.component';
import CurrencyInput from '../form-items-formatted/currency-form-item.component';
import JobSearchSelect from '../job-search-select/job-search-select.component';
import LayoutFormRow from '../layout-form-row/layout-form-row.component';
import VendorSearchSelect from '../vendor-search-select/vendor-search-select.component';
import BillFormLines from './bill-form.lines.component';
import { CalculateBillTotal } from './bill-form.totals.utility';
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -41,20 +41,20 @@ export function BillFormComponent({
job,
loadOutstandingReturns,
loadInventory,
preferredMake
preferredMake,
}) {
const { t } = useTranslation();
const client = useApolloClient();
const [discount, setDiscount] = useState(0);
const {treatments: {Extended_Bill_Posting, ClosingPeriod}} = useSplitTreatments({
const {
treatments: { Extended_Bill_Posting, ClosingPeriod },
} = useSplitTreatments({
attributes: {},
names: ["Extended_Bill_Posting", "ClosingPeriod"],
names: ['Extended_Bill_Posting', 'ClosingPeriod'],
splitKey: bodyshop.imexshopid,
});
const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount);
@@ -62,7 +62,7 @@ export function BillFormComponent({
!billEdit &&
loadOutstandingReturns({
variables: {
jobId: form.getFieldValue("jobid"),
jobId: form.getFieldValue('jobid'),
vendorId: opt.value,
},
});
@@ -71,7 +71,7 @@ export function BillFormComponent({
const handleFederalTaxExemptSwitchToggle = (checked) => {
// Early gate
if (!checked) return;
const values = form.getFieldsValue("billlines");
const values = form.getFieldsValue('billlines');
// Gate bill lines
if (!values?.billlines?.length) return;
@@ -83,23 +83,21 @@ export function BillFormComponent({
};
useEffect(() => {
if (job) form.validateFields(["is_credit_memo"]);
if (job) form.validateFields(['is_credit_memo']);
}, [job, form]);
useEffect(() => {
const vendorId = form.getFieldValue("vendorid");
const vendorId = form.getFieldValue('vendorid');
if (vendorId && vendorAutoCompleteOptions) {
const matchingVendors = vendorAutoCompleteOptions.filter(
(v) => v.id === vendorId
);
const matchingVendors = vendorAutoCompleteOptions.filter((v) => v.id === vendorId);
if (matchingVendors.length === 1) {
setDiscount(matchingVendors[0].discount);
}
}
const jobId = form.getFieldValue("jobid");
const jobId = form.getFieldValue('jobid');
if (jobId) {
loadLines({ variables: { id: jobId } });
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
if (form.getFieldValue('is_credit_memo') && vendorId && !billEdit) {
loadOutstandingReturns({
variables: {
jobId: jobId,
@@ -126,17 +124,13 @@ export function BillFormComponent({
return (
<div>
<FormFieldsChanged form={form} />
<Form.Item
style={{display: "none"}}
name="isinhouse"
valuePropName="checked"
>
<Form.Item style={{ display: 'none' }} name="isinhouse" valuePropName="checked">
<Switch />
</Form.Item>
<LayoutFormRow grow>
<Form.Item
name="jobid"
label={t("bills.fields.ro_number")}
label={t('bills.fields.ro_number')}
rules={[
{
required: true,
@@ -149,13 +143,19 @@ export function BillFormComponent({
convertedOnly
notExported={false}
onBlur={() => {
if (form.getFieldValue("jobid") !== null && form.getFieldValue("jobid") !== undefined) {
loadLines({variables: {id: form.getFieldValue("jobid")}});
if (form.getFieldValue("vendorid") !== null && form.getFieldValue("vendorid") !== undefined) {
if (
form.getFieldValue('jobid') !== null &&
form.getFieldValue('jobid') !== undefined
) {
loadLines({ variables: { id: form.getFieldValue('jobid') } });
if (
form.getFieldValue('vendorid') !== null &&
form.getFieldValue('vendorid') !== undefined
) {
loadOutstandingReturns({
variables: {
jobId: form.getFieldValue("jobid"),
vendorId: form.getFieldValue("vendorid"),
jobId: form.getFieldValue('jobid'),
vendorId: form.getFieldValue('vendorid'),
},
});
}
@@ -164,7 +164,7 @@ export function BillFormComponent({
/>
</Form.Item>
<Form.Item
label={t("bills.fields.vendor")}
label={t('bills.fields.vendor')}
name="vendorid"
// style={{ display: billEdit ? "none" : null }}
rules={[
@@ -174,12 +174,8 @@ export function BillFormComponent({
},
({ getFieldValue }) => ({
validator(rule, value) {
if (
value &&
!getFieldValue(["isinhouse"]) &&
value === bodyshop.inhousevendorid
) {
return Promise.reject(t("bills.validation.manualinhouse"));
if (value && !getFieldValue(['isinhouse']) && value === bodyshop.inhousevendorid) {
return Promise.reject(t('bills.validation.manualinhouse'));
}
return Promise.resolve();
},
@@ -203,7 +199,7 @@ export function BillFormComponent({
type="warning"
message={
<Space>
{t("bills.labels.iouexists")}
{t('bills.labels.iouexists')}
<Link
target="_blank"
rel="noopener noreferrer"
@@ -220,7 +216,7 @@ export function BillFormComponent({
))}
<LayoutFormRow>
<Form.Item
label={t("bills.fields.invoice_number")}
label={t('bills.fields.invoice_number')}
name="invoice_number"
validateTrigger="onBlur"
hasFeedback
@@ -231,7 +227,7 @@ export function BillFormComponent({
},
({ getFieldValue }) => ({
async validator(rule, value) {
const vendorid = getFieldValue("vendorid");
const vendorid = getFieldValue('vendorid');
if (vendorid && value) {
const response = await client.query({
query: CHECK_BILL_INVOICE_NUMBER,
@@ -245,14 +241,11 @@ export function BillFormComponent({
return Promise.resolve();
} else if (
response.data.bills_aggregate.nodes.length === 1 &&
response.data.bills_aggregate.nodes[0].id ===
form.getFieldValue("id")
response.data.bills_aggregate.nodes[0].id === form.getFieldValue('id')
) {
return Promise.resolve();
}
return Promise.reject(
t("bills.validation.unique_invoice_number")
);
return Promise.reject(t('bills.validation.unique_invoice_number'));
} else {
return Promise.resolve();
}
@@ -263,7 +256,7 @@ export function BillFormComponent({
<Input disabled={disabled || disableInvNumber} />
</Form.Item>
<Form.Item
label={t("bills.fields.date")}
label={t('bills.fields.date')}
name="date"
rules={[
{
@@ -272,29 +265,22 @@ export function BillFormComponent({
},
({ getFieldValue }) => ({
validator(rule, value) {
if (
ClosingPeriod.treatment === "on" &&
bodyshop.accountingconfig.ClosingPeriod
) {
if (ClosingPeriod.treatment === 'on' && bodyshop.accountingconfig.ClosingPeriod) {
if (
dayjs(value)
.startOf("day")
.startOf('day')
.isSameOrAfter(
dayjs(
bodyshop.accountingconfig.ClosingPeriod[0]
).startOf("day")
dayjs(bodyshop.accountingconfig.ClosingPeriod[0]).startOf('day')
) &&
dayjs(value)
.startOf("day")
.startOf('day')
.isSameOrBefore(
dayjs(
bodyshop.accountingconfig.ClosingPeriod[1]
).endOf("day")
dayjs(bodyshop.accountingconfig.ClosingPeriod[1]).endOf('day')
)
) {
return Promise.resolve();
} else {
return Promise.reject(t("bills.validation.closingperiod"));
return Promise.reject(t('bills.validation.closingperiod'));
}
} else {
return Promise.resolve();
@@ -306,17 +292,13 @@ export function BillFormComponent({
<FormDatePicker disabled={disabled} />
</Form.Item>
<Form.Item
label={t("bills.fields.is_credit_memo")}
label={t('bills.fields.is_credit_memo')}
name="is_credit_memo"
valuePropName="checked"
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (
value === true &&
getFieldValue("jobid") &&
getFieldValue("vendorid")
) {
if (value === true && getFieldValue('jobid') && getFieldValue('vendorid')) {
//Removed as this would cause an additional reload when validating the form on submit and clear the values.
// loadOutstandingReturns({
// variables: {
@@ -334,7 +316,7 @@ export function BillFormComponent({
job.status === bodyshop.md_ro_statuses.default_void) &&
(value === false || !value)
) {
return Promise.reject(t("bills.labels.onlycmforinvoiced"));
return Promise.reject(t('bills.labels.onlycmforinvoiced'));
}
return Promise.resolve();
@@ -345,7 +327,7 @@ export function BillFormComponent({
<Switch />
</Form.Item>
<Form.Item
label={t("bills.fields.total")}
label={t('bills.fields.total')}
name="total"
rules={[
{
@@ -357,8 +339,8 @@ export function BillFormComponent({
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
{!billEdit && (
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{width: "10rem"}} disabled={disabled} allowClear>
<Form.Item label={t('bills.fields.allpartslocation')} name="location">
<Select style={{ width: '10rem' }} disabled={disabled} allowClear>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
@@ -369,115 +351,99 @@ export function BillFormComponent({
)}
</LayoutFormRow>
<LayoutFormRow>
{
InstanceRenderManager({imex:
<Form.Item
span={3}
label={t("bills.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<CurrencyInput min={0} disabled={disabled} />
</Form.Item> })
}
<Form.Item
span={3}
label={t("bills.fields.state_tax_rate")}
name="state_tax_rate"
>
{InstanceRenderManager({
imex: (
<Form.Item span={3} label={t('bills.fields.federal_tax_rate')} name="federal_tax_rate">
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
{
InstanceRenderManager({imex: <>
<Form.Item
span={3}
label={t("bills.fields.local_tax_rate")}
name="local_tax_rate"
>
),
})}
<Form.Item span={3} label={t('bills.fields.state_tax_rate')} name="state_tax_rate">
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
{InstanceRenderManager({
imex: (
<>
<Form.Item span={3} label={t('bills.fields.local_tax_rate')} name="local_tax_rate">
<CurrencyInput min={0} />
</Form.Item>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
<Form.Item
span={2}
label={t("bills.labels.federal_tax_exempt")}
label={t('bills.labels.federal_tax_exempt')}
name="federal_tax_exempt"
>
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
</Form.Item>
) : null}
</>})
}
</>
),
})}
<Form.Item shouldUpdate span={13}>
{() => {
const values = form.getFieldsValue([
"billlines",
"total",
"federal_tax_rate",
"state_tax_rate",
"local_tax_rate",
'billlines',
'total',
'federal_tax_rate',
'state_tax_rate',
'local_tax_rate',
]);
let totals;
if (
!!values.total &&
!!values.billlines &&
values.billlines.length > 0
)
if (!!values.total && !!values.billlines && values.billlines.length > 0)
totals = CalculateBillTotal(values);
if (!!totals)
return (
<div align="right">
<Space wrap>
<Statistic
title={t("bills.labels.subtotal")}
title={t('bills.labels.subtotal')}
value={totals.subtotal.toFormat()}
precision={2}
/>
{
InstanceRenderManager({imex: <Statistic
title={t("bills.labels.federal_tax")}
{InstanceRenderManager({
imex: (
<Statistic
title={t('bills.labels.federal_tax')}
value={totals.federalTax.toFormat()}
precision={2}
/> })
}
/>
),
})}
<Statistic
title={t("bills.labels.state_tax")}
title={t('bills.labels.state_tax')}
value={totals.stateTax.toFormat()}
precision={2}
/>
{
InstanceRenderManager({imex: <Statistic
title={t("bills.labels.local_tax")}
{InstanceRenderManager({
imex: (
<Statistic
title={t('bills.labels.local_tax')}
value={totals.localTax.toFormat()}
precision={2}
/>})
}
/>
),
})}
<Statistic
title={t("bills.labels.entered_total")}
title={t('bills.labels.entered_total')}
value={totals.enteredTotal.toFormat()}
precision={2}
/>
<Statistic
title={t("bills.labels.bill_total")}
title={t('bills.labels.bill_total')}
value={totals.invoiceTotal.toFormat()}
precision={2}
/>
<Statistic
title={t("bills.labels.discrepancy")}
title={t('bills.labels.discrepancy')}
valueStyle={{
color:
totals.discrepancy.getAmount() === 0
? "green"
: "red",
color: totals.discrepancy.getAmount() === 0 ? 'green' : 'red',
}}
value={totals.discrepancy.toFormat()}
precision={2}
/>
</Space>
{form.getFieldValue("is_credit_memo") ? (
<AlertComponent
type="warning"
message={t("bills.labels.enteringcreditmemo")}
/>
{form.getFieldValue('is_credit_memo') ? (
<AlertComponent type="warning" message={t('bills.labels.enteringcreditmemo')} />
) : null}
</div>
);
@@ -485,9 +451,9 @@ export function BillFormComponent({
}}
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
<Divider orientation="left">{t('bills.labels.bill_lines')}</Divider>
{Extended_Bill_Posting.treatment === "on" ? (
{Extended_Bill_Posting.treatment === 'on' ? (
<BillFormLinesExtended
lineData={lineData}
discount={discount}
@@ -505,11 +471,13 @@ export function BillFormComponent({
billEdit={billEdit}
/>
)}
<Divider orientation="left" style={{ display: billEdit ? 'none' : null }}>
{t('documents.labels.upload')}
</Divider>
<Form.Item
name="upload"
label="Upload"
style={{display: billEdit ? "none" : null}}
style={{ display: billEdit ? 'none' : null }}
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
@@ -518,19 +486,12 @@ export function BillFormComponent({
return e && e.fileList;
}}
>
<Upload.Dragger
multiple={true}
name="logo"
beforeUpload={() => false}
listType="picture"
>
<Upload.Dragger multiple={true} name="logo" beforeUpload={() => false} listType="picture">
<>
<p className="ant-upload-drag-icon">
<UploadOutlined />
</p>
<p className="ant-upload-text">
Click or drag files to this area to upload.
</p>
<p className="ant-upload-text">Click or drag files to this area to upload.</p>
</>
</Upload.Dragger>
</Form.Item>

View File

@@ -690,13 +690,13 @@ export function BillEnterModalLinesComponent({
formItemProps: (field) => {
return {
key: `${field.index}fedtax`,
valuePropName: "checked",
// initialValue: true,
name: [
field.name,
"applicable_taxes",
"federal",
],
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

@@ -60,6 +60,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.in"),
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 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();
if (!!!result.errors) {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentchange(operation, name),
type: "jobassignmentchange",
});
if (!!result.errors) {
} else {
notification["error"]({
message: t("jobs.errors.assigning", {
message: JSON.stringify(result.errors),
@@ -68,17 +68,19 @@ export function JobEmployeeAssignmentsContainer({
variables: {jobId: job.id, job: {[empAssignment]: null}},
});
if (!!result.errors) {
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),
}),
});
}
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentremoved(operation),
type: "jobassignmentremoved",});
setLoading(false);
};

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,8 +30,28 @@ export function JobTotalsTableTotals({bodyshop, job}) {
total: job.job_totals.totals.subtotal,
bold: true,
},
...(job.job_totals.totals.us_sales_tax_breakdown
...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: `${
@@ -130,7 +151,9 @@ export function JobTotalsTableTotals({bodyshop, job}) {
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,16 +30,22 @@ 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"];
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"];
ret.profitcenter_part = defaults.profits['PAO'];
} else {
ret.profitcenter_part = null;
}

View File

@@ -1,22 +1,22 @@
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,
@@ -28,84 +28,60 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
return (
<div>
<FormRow>
<Form.Item label={t("jobs.fields.class")} name="class">
<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"
>
<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"
>
{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
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"
>
<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"
>
<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"
>
<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"
>
<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" && (
{bodyshop.region_config === 'CA_BC' && (
<Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<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")}
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}
>
<Form.Item noStyle shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}>
{() => {
if (form.getFieldValue("auto_add_ats"))
if (form.getFieldValue('auto_add_ats'))
return (
<Form.Item
label={t("jobs.fields.rate_ats")}
label={t('jobs.fields.rate_ats')}
name="rate_ats"
initialValue={bodyshop.shoprates.rate_atp}
>
@@ -117,19 +93,13 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
}}
</Form.Item>
</FormRow>
{
InstanceRenderManager({imex:
{InstanceRenderManager({
imex: (
<FormRow>
<Form.Item
label={t("jobs.fields.federal_tax_rate")}
name="federal_tax_rate"
>
<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"
>
<Form.Item label={t('jobs.fields.state_tax_rate')} name="state_tax_rate">
<InputNumber
min={0}
max={1}
@@ -138,96 +108,87 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
autoComplete="new-password"
/>
</Form.Item>
<Form.Item
label={t("jobs.fields.local_tax_rate")}
name="local_tax_rate"
>
<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") && (
{bodyshop.region_config.toLowerCase().startsWith('ca') && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
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")}
</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"
>
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<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">
<Form.Item label={t('jobs.fields.rate_ma3s')} name="rate_ma3s">
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
{
@@ -238,12 +199,14 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
// <CurrencyInput min={0}disabled={jobRO} />
// </Form.Item>
}
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
<Form.Item label={t('jobs.fields.rate_matd')} name="rate_matd">
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
</FormRow>
{
InstanceRenderManager({rome: <>
{InstanceRenderManager({
imex: <JobsDetailRatesParts form={form} />,
rome: (
<>
<Divider orientation="left">Tax Profile</Divider>
<JobsDetailRatesProfileOVerride form={form} />
<JobsDetailRatesParts form={form} />
@@ -251,9 +214,10 @@ export function JobsDetailRates({jobRO, form, job, bodyshop}) {
<JobsDetailRatesMaterials form={form} />
<JobsDetailRatesOther form={form} />
<JobsDetailRatesTaxes form={form} />
</>})
}
</>
),
promanager: "USE_ROME"
})}
</div>
);
}

View File

@@ -34,7 +34,7 @@ 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;
@@ -45,6 +45,7 @@ export function PartDispatchTableComponent({
name: Templates.parts_dispatch.key,
variables: { id: record.id },
}}
messageObject={{ subject: Templates.parts_dispatch.subject }}
/>
</Space>
);

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

@@ -242,7 +242,7 @@ export function ShopInfoResponsibilityCenterComponent({bodyshop, form}) {
</Form.Item>
<Space align="center">
d
<DeleteFilled
onClick={() => {
remove(field.name);
}}

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 = [
{
...(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,7 +1,7 @@
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;
@@ -23,9 +23,7 @@ const VendorSearchSelect = (
preferredMake && options
? options.filter(
(o) =>
o.favorite.filter(
(f) => f.toLowerCase() === preferredMake.toLowerCase()
).length > 0
o.favorite.filter((f) => f.toLowerCase() === preferredMake.toLowerCase()).length > 0
)
: [];
@@ -35,30 +33,34 @@ const VendorSearchSelect = (
showSearch
value={option}
style={{
width: "100%",
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>
{discount && discount !== 0 ? <Tag color="green">{`${discount * 100}%`}</Tag> : null}
</div>
);
}}
popupMatchSelectWidth={false}
onChange={setOption}
optionFilterProp="name"
onSelect={onSelect}
disabled={disabled || false}
optionLabelProp={"name"}
optionLabelProp={'name'}
>
{favorites
? favorites.map((o) => (
<Option
key={`favorite-${o.id}`}
value={o.id}
name={o.name}
discount={o.discount}
>
<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>
)}
<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}
@@ -70,20 +72,17 @@ const VendorSearchSelect = (
{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 className="imex-flex-row" style={{ width: '100%' }}>
<div style={{ flex: 1 }}>{o.name}</div>
<Space style={{marginLeft: "1rem"}}>
{o.phone && showPhone && (
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>
)}
<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>

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,9 +162,7 @@ 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}/>
),
render: (text, record) => <Checkbox checked={record.successful} />,
},
{
title: t("general.labels.message"),

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,64 +1,72 @@
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 mapStateToProps = createStructuredSelector({
bodyshop:selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export function TimeTicketsContainer({
bodyshop,
setBreadcrumbs,
setSelectedHeader,
}) {
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 startDate = start
? dayjs(start)
: dayjs().startOf("week").subtract(7, "day");
const endDate = end ? dayjs(end) : dayjs().endOf("week");
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",
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");
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"),
link: '/manage/timetickets',
label: t('titles.bc.timetickets'),
},
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
@@ -66,7 +74,7 @@ export function TimeTicketsContainer({
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<FeatureWrapperComponent featureName='timetickets'>
<FeatureWrapperComponent featureName="timetickets">
<RbacWrapper action="timetickets:list">
<Row gutter={[16, 16]}>
<Col span={24}>
@@ -77,7 +85,9 @@ export function TimeTicketsContainer({
<Space wrap>
<TimeTicketsAttendanceTable />
<TimeTicketsPayrollTable />
{Enhanced_Payroll.treatment === 'on' && (
<TimeTicketsCommit timetickets={data ? data.timetickets : []} />
)}
<TimeTicketsDatesSelector />
</Space>
}
@@ -97,7 +107,4 @@ export function TimeTicketsContainer({
);
}
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({
.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 &&
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 "),
.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) {