Merged in development (pull request #37) SIT 1 Bug Fixes

IO-763 IO-773 Label updates.
IO-765 Resolve bill save issues.
IO-767 Resolve document mapping on parts return
IO-768 Update Backorder Parts ETA
IO-771 Reassign documents between jobs.
IO-772 RBAC for RBAC Controls
IO-769 Label change for CC.
IO-779 Added shop sub status and blockers.
IO-770 Remove seconds from shop config.
This commit is contained in:
Patrick Fic
2021-03-16 21:56:24 +00:00
24 changed files with 631 additions and 280 deletions

View File

@@ -11882,6 +11882,32 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<folder_node>
<name>sub_status</name>
<children>
<concept_node>
<name>expired</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>
</children>
</folder_node>
<concept_node> <concept_node>
<name>sunday</name> <name>sunday</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13420,6 +13446,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>PAG</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>PAL</name> <name>PAL</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -41,7 +41,6 @@ export default function BillDetailEditcontainer() {
billlines.forEach((il) => { billlines.forEach((il) => {
delete il.__typename; delete il.__typename;
if (il.id) { if (il.id) {
updates.push( updates.push(
updateBillLine({ updateBillLine({
@@ -71,8 +70,11 @@ export default function BillDetailEditcontainer() {
); );
} }
}); });
await Promise.all(updates); await Promise.all(updates);
await refetch();
form.resetFields();
form.resetFields();
setUpdateLoading(false); setUpdateLoading(false);
}; };
@@ -92,32 +94,7 @@ export default function BillDetailEditcontainer() {
<Form <Form
form={form} form={form}
onFinish={handleFinish} onFinish={handleFinish}
initialValues={ initialValues={transformData(data)}
data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) ||
false,
state:
(i.applicable_taxes && i.applicable_taxes.state) ||
false,
local:
(i.applicable_taxes && i.applicable_taxes.local) ||
false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {}
}
> >
<Button <Button
htmlType="submit" htmlType="submit"
@@ -138,3 +115,25 @@ export default function BillDetailEditcontainer() {
</LoadingSkeleton> </LoadingSkeleton>
); );
} }
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -3,7 +3,7 @@ import React, { useEffect } from "react";
export default function JiraSupportComponent() { export default function JiraSupportComponent() {
useScript(); useScript();
return <div>Jira Temp Widget</div>; return <div></div>;
} }
const useScript = () => { const useScript = () => {

View File

@@ -35,7 +35,7 @@ export default function JobReconciliationBillsTable({
}`, }`,
}, },
{ {
title: t("billlines.fields.actua_price"), title: t("billlines.fields.actual_price"),
dataIndex: "actual_price", dataIndex: "actual_price",
key: "actual_price", key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price, sorter: (a, b) => a.actual_price - b.actual_price,

View File

@@ -54,11 +54,20 @@ export default function JobsDocumentsGalleryReassign({ galleryImages }) {
const res = await axios.post("/media/rename", { const res = await axios.post("/media/rename", {
documents: selectedImages.map((i) => { documents: selectedImages.map((i) => {
return { id: i.id, from: i.key, to: i.key.replace("null", jobid) }; //Need to check if the current key folder is null, or another job.
const currentKeys = i.key.split("/");
currentKeys[1] = jobid;
currentKeys.join("/");
return { id: i.id, from: i.key, to: currentKeys.join("/") };
}), }),
}); });
console.log(res.data); res.data
.filter((d) => d.error)
.forEach((d) => {
notification["error"]({ message: t("documents.errors.updating") });
console.error("Error updating job document", d);
});
const proms = []; const proms = [];

View File

@@ -0,0 +1,91 @@
import { useMutation } from "@apollo/client";
import { Button, Form, notification, Popover, Spin } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import { CalendarFilled } from "@ant-design/icons";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function PartsOrderBackorderEta({
backordered_eta,
partsOrderStatus,
partsLineId,
jobLineId,
disabled,
bodyshop,
}) {
const [visibility, setVisibility] = useState(false);
const [loading, setLoading] = useState(false);
const [updateBoDate] = useMutation(MUTATION_UPDATE_BO_ETA);
const { t } = useTranslation();
const [form] = Form.useForm();
const isAlreadyBackordered =
bodyshop.md_order_statuses.default_bo === partsOrderStatus;
const handleFinish = async (values) => {
setLoading(true);
logImEXEvent("job_parts_backorder_update_eta");
const result = await updateBoDate({
variables: {
partsLineId,
partsOrder: { backordered_eta: values.eta },
},
});
if (!!result.errors) {
notification["error"]({
message: t("parts_orders.errors.backordering", {
message: JSON.stringify(result.errors),
}),
});
}
setVisibility(false);
setLoading(false);
};
const handlePopover = (e) => {
setVisibility(true);
};
const popContent = (
<div>
<Form form={form} onFinish={handleFinish}>
<Form.Item name="eta">
<FormDatePicker />
</Form.Item>
<Button type="primary" onClick={() => form.submit()}>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisibility(false)}>Close</Button>
</Form>
</div>
);
return (
<Popover
destroyTooltipOnHide
content={popContent}
visible={visibility}
disabled={disabled}
>
<DateFormatter>{backordered_eta}</DateFormatter>
{isAlreadyBackordered && (
<CalendarFilled style={{ cursor: "pointer" }} onClick={handlePopover} />
)}
{loading && <Spin size="small" />}
</Popover>
);
}
export default connect(mapStateToProps, null)(PartsOrderBackorderEta);

View File

@@ -14,6 +14,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component"; import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component"; import PrintWrapper from "../print-wrapper/print-wrapper.component";
@@ -164,13 +165,13 @@ export function PartsOrderListTableComponent({
</Button> </Button>
<PrintWrapper <PrintWrapper
templateObject={{ templateObject={{
name: record.isReturn name: record.return
? Templates.parts_return_slip.key ? Templates.parts_return_slip.key
: Templates.parts_order.key, : Templates.parts_order.key,
variables: { id: record.id }, variables: { id: record.id },
}} }}
messageObject={{ messageObject={{
subject: record.isReturn subject: record.return
? Templates.parts_return_slip.subject ? Templates.parts_return_slip.subject
: Templates.parts_order.subject, : Templates.parts_order.subject,
}} }}
@@ -245,7 +246,15 @@ export function PartsOrderListTableComponent({
title: t("parts_orders.fields.backordered_eta"), title: t("parts_orders.fields.backordered_eta"),
dataIndex: "backordered_eta", dataIndex: "backordered_eta",
key: "backordered_eta", key: "backordered_eta",
render: (text, record) => <DateFormatter>{text}</DateFormatter>, render: (text, record) => (
<PartsOrderBackorderEta
backordered_eta={record.backordered_eta}
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
),
}, },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),

View File

@@ -48,7 +48,7 @@ const ret = {
"shiftclock:view": 2, "shiftclock:view": 2,
"shop:vendors": 2, "shop:vendors": 2,
"shop:rbac": 5, "shop:rbac": 1,
"shop:templates": 4, "shop:templates": 4,
"temporarydocs:view": 2, "temporarydocs:view": 2,

View File

@@ -28,10 +28,12 @@ function RbacWrapper({
...restProps ...restProps
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
if ( if (
(requiredAuthLevel && requiredAuthLevel <= authLevel) || (requiredAuthLevel && requiredAuthLevel <= authLevel) ||
(bodyshop.md_rbac && bodyshop.md_rbac[action] <= authLevel) || ((bodyshop.md_rbac && bodyshop.md_rbac[action]) || rbacDefaults[action]) <=
(!!!bodyshop.md_rbac && rbacDefaults[action] <= authLevel) authLevel ||
(!bodyshop.md_rbac && rbacDefaults[action] <= authLevel)
) )
return <div>{React.cloneElement(children, restProps)}</div>; return <div>{React.cloneElement(children, restProps)}</div>;

View File

@@ -2,11 +2,12 @@ import { Form, InputNumber } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
export default function ShopInfoRbacComponent({ form }) { export default function ShopInfoRbacComponent({ form }) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div> <RbacWrapper action="shop:rbac">
<LayoutFormRow> <LayoutFormRow>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")} label={t("bodyshop.fields.rbac.accounting.payables")}
@@ -537,6 +538,6 @@ export default function ShopInfoRbacComponent({ form }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
</div> </RbacWrapper>
); );
} }

View File

@@ -30,7 +30,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
}, },
]} ]}
> >
<TimePicker showSecond={false} /> <TimePicker showSecond={false} format="hh:mm" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.schedule_end_time")} label={t("bodyshop.fields.schedule_end_time")}
@@ -42,7 +42,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
}, },
]} ]}
> >
<TimePicker showSecond={false} /> <TimePicker showSecond={false} format="hh:mm" />
</Form.Item> </Form.Item>
<strong>{t("bodyshop.labels.apptcolors")}</strong> <strong>{t("bodyshop.labels.apptcolors")}</strong>
<Row> <Row>

View File

@@ -0,0 +1,33 @@
import { Button, Result } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopSubStatus);
export function ShopSubStatus({ bodyshop }) {
const { t } = useTranslation();
const { sub_status } = bodyshop;
return (
<Result
status="403"
title={t(`general.labels.sub_status.${sub_status}`)}
extra={
<Button
onClick={() => {
alert("Not implemented yet.");
}}
>
{t("general.actions.submitticket")}
</Button>
}
/>
);
}

View File

@@ -80,6 +80,7 @@ export const QUERY_BODYSHOP = gql`
use_fippa use_fippa
md_payment_types md_payment_types
md_hour_split md_hour_split
sub_status
employees { employees {
id id
first_name first_name
@@ -158,6 +159,7 @@ export const UPDATE_SHOP = gql`
use_fippa use_fippa
md_payment_types md_payment_types
md_hour_split md_hour_split
sub_status
employees { employees {
id id
first_name first_name

View File

@@ -10,6 +10,24 @@ export const INSERT_NEW_PARTS_ORDERS = gql`
} }
`; `;
export const MUTATION_UPDATE_BO_ETA = gql`
mutation MUTATION_UPDATE_BO_ETA(
$partsLineId: uuid!
$partsOrder: parts_order_lines_set_input
) {
update_parts_order_lines(
where: { id: { _eq: $partsLineId } }
_set: $partsOrder
) {
returning {
status
backordered_eta
id
}
}
}
`;
export const MUTATION_BACKORDER_PART_LINE = gql` export const MUTATION_BACKORDER_PART_LINE = gql`
mutation MUTATION_BACKORDER_PART_LINE( mutation MUTATION_BACKORDER_PART_LINE(
$jobLineId: uuid! $jobLineId: uuid!

View File

@@ -12,8 +12,10 @@ import ChatAffixContainer from "../../components/chat-affix/chat-affix.container
import ConflictComponent from "../../components/conflict/conflict.component"; import ConflictComponent from "../../components/conflict/conflict.component";
import ErrorBoundary from "../../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
import FcmNotification from "../../components/fcm-notification/fcm-notification.component"; import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
//import FooterComponent from "../../components/footer/footer.component"; //import FooterComponent from "../../components/footer/footer.component";
//Component Imports //Component Imports
import HeaderContainer from "../../components/header/header.container"; import HeaderContainer from "../../components/header/header.container";
import JiraSupportComponent from "../../components/jira-support-widget/jira-support-widget.component"; import JiraSupportComponent from "../../components/jira-support-widget/jira-support-widget.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
@@ -21,7 +23,10 @@ import PartnerPingComponent from "../../components/partner-ping/partner-ping.com
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container"; import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import TestComponent from "../../components/_test/test.component"; import TestComponent from "../../components/_test/test.component";
import { QUERY_STRIPE_ID } from "../../graphql/bodyshop.queries"; import { QUERY_STRIPE_ID } from "../../graphql/bodyshop.queries";
import { selectInstanceConflict } from "../../redux/user/user.selectors"; import {
selectBodyshop,
selectInstanceConflict,
} from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient"; import client from "../../utils/GraphQLClient";
import "./manage.page.styles.scss"; import "./manage.page.styles.scss";
@@ -166,15 +171,198 @@ const stripePromise = new Promise((resolve, reject) => {
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
conflict: selectInstanceConflict, conflict: selectInstanceConflict,
bodyshop: selectBodyshop,
}); });
export function Manage({ match, conflict }) { export function Manage({ match, conflict, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => { useEffect(() => {
document.title = t("titles.app"); document.title = t("titles.app");
}, [t]); }, [t]);
const AppRouteTable = (
<Suspense
fallback={<LoadingSpinner message={t("general.labels.loadingapp")} />}
>
<Elements stripe={stripePromise}>
<PaymentModalContainer />
</Elements>
<BreadCrumbs />
<BillEnterModalContainer />
<JobCostingModal />
<ReportCenterModal />
<EmailOverlayContainer />
<TimeTicketModalContainer />
<PrintCenterModalContainer />
<Route exact path={`${match.path}/_test`} component={TestComponent} />
<Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch>
<Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/deliver`}
component={JobDeliver}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/checklist`}
component={JobChecklistView}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/admin`}
component={JobsAdmin}
/>
<Route exact path={`${match.path}/jobs/all`} component={AllJobs} />
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route path={`${match.path}/jobs/:jobId`} component={JobsDetailPage} />
</Switch>
<Route exact path={`${match.path}/temporarydocs/`} component={TempDocs} />
<Route
exact
path={`${match.path}/courtesycars/`}
component={CourtesyCarsPage}
/>
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts`}
component={ContractsList}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/new`}
component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/>
<Route
exact
path={`${match.path}/courtesycars/:ccId`}
component={CourtesyCarDetailContainer}
/>
</Switch>
<Route exact path={`${match.path}/profile`} component={ProfilePage} />
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route exact path={`${match.path}/bills`} component={BillsListPage} />
<Route exact path={`${match.path}/owners`} component={OwnersContainer} />
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
{
// <Route
// exact
// path={`${match.path}/shop/templates`}
// component={ShopTemplates}
// />
}
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
<Route
exact
path={`${match.path}/shop/csi`}
component={ShopCsiPageContainer}
/>
<Route
exact
path={`${match.path}/accounting/receivables`}
component={AccountingReceivables}
/>
<Route
exact
path={`${match.path}/accounting/payables`}
component={AccountingPayables}
/>
<Route
exact
path={`${match.path}/accounting/payments`}
component={AccountingPayments}
/>
<Route exact path={`${match.path}/partsqueue`} component={PartsQueue} />
<Route exact path={`${match.path}/payments`} component={PaymentsAll} />
<Route exact path={`${match.path}/shiftclock`} component={ShiftClock} />
<Route exact path={`${match.path}/scoreboard`} component={Scoreboard} />
<Route
exact
path={`${match.path}/timetickets`}
component={TimeTicketsAll}
/>
<Route exact path={`${match.path}/help`} component={Help} />
<Route exact path={`${match.path}/emailtest`} component={EmailTest} />
</Suspense>
);
let PageContent;
if (conflict) PageContent = <ConflictComponent />;
else if (bodyshop && bodyshop.sub_status !== "active")
PageContent = <ShopSubStatusComponent />;
else PageContent = AppRouteTable;
return ( return (
<Layout className="layout-container"> <Layout className="layout-container">
<Header> <Header>
@@ -183,233 +371,7 @@ export function Manage({ match, conflict }) {
<Content className="content-container"> <Content className="content-container">
<FcmNotification /> <FcmNotification />
<PartnerPingComponent /> <PartnerPingComponent />
<ErrorBoundary> <ErrorBoundary>{PageContent}</ErrorBoundary>
{conflict ? (
<ConflictComponent />
) : (
<Suspense
fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} />
}
>
<Elements stripe={stripePromise}>
<PaymentModalContainer />
</Elements>
<BreadCrumbs />
<BillEnterModalContainer />
<JobCostingModal />
<ReportCenterModal />
<EmailOverlayContainer />
<TimeTicketModalContainer />
<PrintCenterModalContainer />
<Route
exact
path={`${match.path}/_test`}
component={TestComponent}
/>
<Route exact path={`${match.path}`} component={ManageRootPage} />
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
<Switch>
<Route
exact
path={`${match.path}/jobs/:jobId/intake`}
component={JobIntake}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/deliver`}
component={JobDeliver}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/checklist`}
component={JobChecklistView}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/close`}
component={JobsClose}
/>
<Route
exact
path={`${match.path}/jobs/:jobId/admin`}
component={JobsAdmin}
/>
<Route
exact
path={`${match.path}/jobs/all`}
component={AllJobs}
/>
<Route
exact
path={`${match.path}/jobs/new`}
component={JobsCreateContainerPage}
/>
<Route
path={`${match.path}/jobs/:jobId`}
component={JobsDetailPage}
/>
</Switch>
<Route
exact
path={`${match.path}/temporarydocs/`}
component={TempDocs}
/>
<Route
exact
path={`${match.path}/courtesycars/`}
component={CourtesyCarsPage}
/>
<Switch>
<Route
exact
path={`${match.path}/courtesycars/new`}
component={CourtesyCarCreateContainer}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts`}
component={ContractsList}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/new`}
component={ContractCreatePage}
/>
<Route
exact
path={`${match.path}/courtesycars/contracts/:contractId`}
component={ContractDetailPage}
/>
<Route
exact
path={`${match.path}/courtesycars/:ccId`}
component={CourtesyCarDetailContainer}
/>
</Switch>
<Route
exact
path={`${match.path}/profile`}
component={ProfilePage}
/>
<Route
exact
path={`${match.path}/vehicles`}
component={VehiclesContainer}
/>
<Route
exact
path={`${match.path}/production/list`}
component={ProductionListPage}
/>
<Route
exact
path={`${match.path}/production/board`}
component={ProductionBoardPage}
/>
<Route
exact
path={`${match.path}/vehicles/:vehId`}
component={VehiclesDetailContainer}
/>
<Route
exact
path={`${match.path}/bills`}
component={BillsListPage}
/>
<Route
exact
path={`${match.path}/owners`}
component={OwnersContainer}
/>
<Route
exact
path={`${match.path}/owners/:ownerId`}
component={OwnersDetailContainer}
/>
<Route
exact
path={`${match.path}/schedule`}
component={ScheduleContainer}
/>
<Route
exact
path={`${match.path}/available`}
component={JobsAvailablePage}
/>
<Route exact path={`${match.path}/shop/`} component={ShopPage} />
{
// <Route
// exact
// path={`${match.path}/shop/templates`}
// component={ShopTemplates}
// />
}
<Route
exact
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
<Route
exact
path={`${match.path}/shop/csi`}
component={ShopCsiPageContainer}
/>
<Route
exact
path={`${match.path}/accounting/receivables`}
component={AccountingReceivables}
/>
<Route
exact
path={`${match.path}/accounting/payables`}
component={AccountingPayables}
/>
<Route
exact
path={`${match.path}/accounting/payments`}
component={AccountingPayments}
/>
<Route
exact
path={`${match.path}/partsqueue`}
component={PartsQueue}
/>
<Route
exact
path={`${match.path}/payments`}
component={PaymentsAll}
/>
<Route
exact
path={`${match.path}/shiftclock`}
component={ShiftClock}
/>
<Route
exact
path={`${match.path}/scoreboard`}
component={Scoreboard}
/>
<Route
exact
path={`${match.path}/timetickets`}
component={TimeTicketsAll}
/>
<Route exact path={`${match.path}/help`} component={Help} />
<Route
exact
path={`${match.path}/emailtest`}
component={EmailTest}
/>
</Suspense>
)}
</ErrorBoundary>
<ChatAffixContainer /> <ChatAffixContainer />
<BackTop /> <BackTop />

View File

@@ -1,15 +1,14 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries"; import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
import { setBodyshop } from "../../redux/user/user.actions"; import { setBodyshop } from "../../redux/user/user.actions";
import ManagePage from "./manage.page.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { useTranslation } from "react-i18next";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import NoShop from "../../components/no-shop/no-shop.component"; import ManagePage from "./manage.page.component";
const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop }); const mapStateToProps = createStructuredSelector({ bodyshop: selectBodyshop });
@@ -32,8 +31,6 @@ function ManagePageContainer({ match, setBodyshop, bodyshop }) {
return <LoadingSpinner message={t("general.labels.loadingshop")} />; return <LoadingSpinner message={t("general.labels.loadingshop")} />;
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
if (bodyshop && bodyshop.notfound) return <NoShop />;
return <ManagePage match={match} />; return <ManagePage match={match} />;
} }

View File

@@ -559,8 +559,8 @@
"plate": "Plate Number", "plate": "Plate Number",
"purchasedate": "Purchase Date", "purchasedate": "Purchase Date",
"registrationexpires": "Registration Expires On", "registrationexpires": "Registration Expires On",
"serviceenddate": "Service End Date", "serviceenddate": "Usage End Date",
"servicestartdate": "Service Start Date", "servicestartdate": "Usage Start Date",
"status": "Status", "status": "Status",
"vin": "VIN", "vin": "VIN",
"year": "Year" "year": "Year"
@@ -764,6 +764,9 @@
"sendagain": "Send Again", "sendagain": "Send Again",
"sendby": "Send By", "sendby": "Send By",
"signin": "Sign In", "signin": "Sign In",
"sub_status": {
"expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. "
},
"sunday": "Sunday", "sunday": "Sunday",
"text": "Text", "text": "Text",
"thursday": "Thursday", "thursday": "Thursday",
@@ -861,6 +864,7 @@
"PAA": "Aftermarket", "PAA": "Aftermarket",
"PAC": "Rechromed", "PAC": "Rechromed",
"PAE": "Existing", "PAE": "Existing",
"PAG": "Glass",
"PAL": "LKQ", "PAL": "LKQ",
"PAM": "Remanufactured", "PAM": "Remanufactured",
"PAN": "New/OEM", "PAN": "New/OEM",

View File

@@ -764,6 +764,9 @@
"sendagain": "", "sendagain": "",
"sendby": "", "sendby": "",
"signin": "", "signin": "",
"sub_status": {
"expired": ""
},
"sunday": "", "sunday": "",
"text": "", "text": "",
"thursday": "", "thursday": "",
@@ -861,6 +864,7 @@
"PAA": "", "PAA": "",
"PAC": "", "PAC": "",
"PAE": "", "PAE": "",
"PAG": "",
"PAL": "", "PAL": "",
"PAM": "", "PAM": "",
"PAN": "", "PAN": "",

View File

@@ -764,6 +764,9 @@
"sendagain": "", "sendagain": "",
"sendby": "", "sendby": "",
"signin": "", "signin": "",
"sub_status": {
"expired": ""
},
"sunday": "", "sunday": "",
"text": "", "text": "",
"thursday": "", "thursday": "",
@@ -861,6 +864,7 @@
"PAA": "", "PAA": "",
"PAC": "", "PAC": "",
"PAE": "", "PAE": "",
"PAG": "",
"PAL": "", "PAL": "",
"PAM": "", "PAM": "",
"PAN": "", "PAN": "",

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "sub_status";
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "sub_status" text NOT NULL DEFAULT
'active';
type: run_sql

View File

@@ -0,0 +1,78 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- logo_img_path
- md_categories
- md_classes
- md_hour_split
- md_ins_cos
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- phone
- prodtargethrs
- production_config
- region_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- stripe_acct_id
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -0,0 +1,79 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- logo_img_path
- md_categories
- md_classes
- md_hour_split
- md_ins_cos
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- phone
- prodtargethrs
- production_config
- region_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- stripe_acct_id
- sub_status
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

View File

@@ -777,6 +777,7 @@ tables:
- state - state
- state_tax_id - state_tax_id
- stripe_acct_id - stripe_acct_id
- sub_status
- target_touchtime - target_touchtime
- template_header - template_header
- textid - textid