Added error handling for receivables screen BOD-138

This commit is contained in:
Patrick Fic
2020-06-02 11:43:08 -07:00
parent 73040064d4
commit a70933f03c
12 changed files with 596 additions and 103 deletions

View File

@@ -7610,6 +7610,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>exporting</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>exporting-partner</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>invoicing</name>
<definition_loaded>false</definition_loaded>
@@ -11604,6 +11646,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>exported</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>invoiced</name>
<definition_loaded>false</definition_loaded>
@@ -11787,6 +11850,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>accounting-receivables</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>activejobs</name>
<definition_loaded>false</definition_loaded>
@@ -14834,6 +14918,27 @@
<folder_node>
<name>titles</name>
<children>
<concept_node>
<name>accounting-receivables</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>app</name>
<definition_loaded>false</definition_loaded>
@@ -14858,6 +14963,27 @@
<folder_node>
<name>bc</name>
<children>
<concept_node>
<name>accounting-receivables</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>availablejobs</name>
<definition_loaded>false</definition_loaded>

View File

@@ -0,0 +1,197 @@
import { Input, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
export default function AccountingReceivablesTableComponent({ loading, jobs }) {
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
search: "",
});
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const columns = [
{
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}>{record.ro_number}</Link>
),
},
{
title: t("jobs.fields.est_number"),
dataIndex: "est_number",
key: "est_number",
sorter: (a, b) => a.est_number - b.est_number,
sortOrder:
state.sortedInfo.columnKey === "est_number" && state.sortedInfo.order,
render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}>{record.est_number}</Link>
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
sorter: (a, b) => a.status - b.status,
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
},
{
title: t("jobs.fields.owner"),
dataIndex: "owner",
key: "owner",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => {
return record.owner ? (
<Link to={"/manage/owners/" + record.owner.id}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}
</Link>
) : (
<span>{`${record.ownr_fn || ""} ${record.ownr_ln || ""}`}</span>
);
},
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link to={"/manage/vehicles/" + record.vehicleid}>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) => {
return record.clm_no ? (
<span>{record.clm_no}</span>
) : (
t("general.labels.unknown")
);
},
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => {
return record.clm_total ? (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
) : (
t("general.labels.unknown")
);
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => (
<div>
<JobExportButton
jobId={record.id}
disabled={!!record.date_exported}
/>
{JSON.stringify(record.date_exported)}
</div>
),
},
];
const handleSearch = (e) => {
setState({ ...state, search: e.target.value });
};
const dataSource = state.search
? jobs.filter(
(v) =>
(v.ro_number || "")
.toString()
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.est_number || "")
.toString()
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.ownr_fn || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.ownr_ln || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.ownr_co_nm || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.v_model_desc || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.v_make_desc || "")
.toLowerCase()
.includes(state.search.toLowerCase()) ||
(v.clm_no || "").toLowerCase().includes(state.search.toLowerCase())
)
: jobs;
return (
<div>
<Table
loading={loading}
title={() => {
return (
<div>
<Input
value={state.search}
onChange={handleSearch}
placeholder={t("general.labels.search")}
allowClear
/>
</div>
);
}}
dataSource={dataSource}
size="small"
pagination={{ position: "top" }}
columns={columns}
rowKey="id"
onChange={handleTableChange}
/>
</div>
);
}

View File

@@ -42,28 +42,29 @@ function Header({
//TODO Add
return (
<Row type='flex' justify='space-around' align='middle'>
<Row type="flex" justify="space-around" align="middle">
{logo ? (
<Col span={3}>
<img alt='Shop Logo' src={logo} style={{ height: "40px" }} />
<img alt="Shop Logo" src={logo} style={{ height: "40px" }} />
</Col>
) : null}
<Col span={21}>
{landingHeader ? (
<Menu
theme='dark'
className='header'
theme="dark"
className="header"
selectedKeys={selectedNavItem}
mode='horizontal'
onClick={handleMenuClick}>
mode="horizontal"
onClick={handleMenuClick}
>
<ManageSignInButton />
<Menu.SubMenu
title={
<div>
<Avatar
size='medium'
alt='Avatar'
size="medium"
alt="Avatar"
src={
currentUser.photoURL ? currentUser.photoURL : UserImage
}
@@ -71,12 +72,13 @@ function Header({
/>
{currentUser.displayName || t("general.labels.unknown")}
</div>
}>
}
>
<Menu.Item onClick={() => signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item>
<Link to='/manage/profile'>
<Link to="/manage/profile">
{t("menus.currentuser.profile")}
</Link>
</Menu.Item>
@@ -86,14 +88,15 @@ function Header({
<GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}>
<Menu.Item actiontype='lang-select' key='en-US'>
}
>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype='lang-select' key='fr-CA'>
<Menu.Item actiontype="lang-select" key="fr-CA">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype='lang-select' key='es-MX'>
<Menu.Item actiontype="lang-select" key="es-MX">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
@@ -101,13 +104,14 @@ function Header({
</Menu>
) : (
<Menu
theme='dark'
className='header'
theme="dark"
className="header"
selectedKeys={selectedNavItem}
mode='horizontal'
onClick={handleMenuClick}>
<Menu.Item key='home'>
<Link to='/manage'>
mode="horizontal"
onClick={handleMenuClick}
>
<Menu.Item key="home">
<Link to="/manage">
<HomeFilled />
{t("menus.header.home")}
</Link>
@@ -118,40 +122,41 @@ function Header({
<Icon component={FaCarCrash} />
<span>{t("menus.header.jobs")}</span>
</span>
}>
<Menu.Item key='schedule'>
<Link to='/manage/schedule'>
}
>
<Menu.Item key="schedule">
<Link to="/manage/schedule">
<Icon component={FaCalendarAlt} />
{t("menus.header.schedule")}
</Link>
</Menu.Item>
<Menu.Item key='production'>
<Link to='/manage/production/list'>
<Menu.Item key="production">
<Link to="/manage/production/list">
<Icon component={FaCalendarAlt} />
{t("menus.header.productionlist")}
</Link>
</Menu.Item>
<Menu.Item key='activejobs'>
<Link to='/manage/jobs'>{t("menus.header.activejobs")}</Link>
<Menu.Item key="activejobs">
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
</Menu.Item>
<Menu.Item key='alljobs'>
<Link to='/manage/jobs/all'>{t("menus.header.alljobs")}</Link>
<Menu.Item key="alljobs">
<Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
</Menu.Item>
<Menu.Item key='availablejobs'>
<Link to='/manage/available'>
<Menu.Item key="availablejobs">
<Link to="/manage/available">
{t("menus.header.availablejobs")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.customers")}>
<Menu.Item key='owners'>
<Link to='/manage/owners'>
<Menu.Item key="owners">
<Link to="/manage/owners">
<TeamOutlined />
{t("menus.header.owners")}
</Link>
</Menu.Item>
<Menu.Item key='vehicles'>
<Link to='/manage/vehicles'>
<Menu.Item key="vehicles">
<Link to="/manage/vehicles">
<CarFilled />
{t("menus.header.vehicles")}
</Link>
@@ -164,21 +169,22 @@ function Header({
<CarFilled />
<span>{t("menus.header.courtesycars")}</span>
</span>
}>
<Menu.Item key='courtesycarsall'>
<Link to='/manage/courtesycars'>
}
>
<Menu.Item key="courtesycarsall">
<Link to="/manage/courtesycars">
<CarFilled />
{t("menus.header.courtesycars-all")}
</Link>
</Menu.Item>
<Menu.Item key='contracts'>
<Link to='/manage/courtesycars/contracts'>
<Menu.Item key="contracts">
<Link to="/manage/courtesycars/contracts">
<FileFilled />
{t("menus.header.courtesycars-contracts")}
</Link>
</Menu.Item>
<Menu.Item key='newcontract'>
<Link to='/manage/courtesycars/contracts/new'>
<Menu.Item key="newcontract">
<Link to="/manage/courtesycars/contracts/new">
<FileAddFilled />
{t("menus.header.courtesycars-newcontract")}
</Link>
@@ -191,43 +197,51 @@ function Header({
<DollarCircleFilled />
<span>{t("menus.header.accounting")}</span>
</span>
}>
}
>
<Menu.Item
key='enterinvoices'
key="enterinvoices"
onClick={() => {
setInvoiceEnterContext({
actions: {},
context: {},
});
}}>
}}
>
{t("menus.header.enterinvoices")}
</Menu.Item>
<Menu.Item key='invoices'>
<Link to='/manage/invoices'>{t("menus.header.invoices")}</Link>
<Menu.Item key="invoices">
<Link to="/manage/invoices">{t("menus.header.invoices")}</Link>
</Menu.Item>
<Menu.Item
key='entertimetickets'
key="entertimetickets"
onClick={() => {
setTimeTicketContext({
actions: {},
context: {},
});
}}>
}}
>
{t("menus.header.entertimeticket")}
</Menu.Item>
<Menu.Item key="receivables">
<Link to="/manage/accounting/receivables">
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item>
</Menu.SubMenu>
<Menu.SubMenu title={t("menus.header.shop")}>
<Menu.Item key='shop'>
<Link to='/manage/shop'>{t("menus.header.shop_config")}</Link>
<Menu.Item key="shop">
<Link to="/manage/shop">{t("menus.header.shop_config")}</Link>
</Menu.Item>
<Menu.Item key='shop-templates'>
<Link to='/manage/shop/templates'>
<Menu.Item key="shop-templates">
<Link to="/manage/shop/templates">
{t("menus.header.shop_templates")}
</Link>
</Menu.Item>
<Menu.Item key='shop-vendors'>
<Link to='/manage/shop/vendors'>
<Menu.Item key="shop-vendors">
<Link to="/manage/shop/vendors">
{t("menus.header.shop_vendors")}
</Link>
</Menu.Item>
@@ -237,8 +251,8 @@ function Header({
title={
<div>
<Avatar
size='medium'
alt='Avatar'
size="medium"
alt="Avatar"
src={
currentUser.photoURL ? currentUser.photoURL : UserImage
}
@@ -246,12 +260,13 @@ function Header({
/>
{currentUser.displayName || t("general.labels.unknown")}
</div>
}>
}
>
<Menu.Item onClick={() => signOutStart()}>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item>
<Link to='/manage/profile'>
<Link to="/manage/profile">
{t("menus.currentuser.profile")}
</Link>
</Menu.Item>
@@ -261,14 +276,15 @@ function Header({
<GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}>
<Menu.Item actiontype='lang-select' key='en-US'>
}
>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype='lang-select' key='fr-CA'>
<Menu.Item actiontype="lang-select" key="fr-CA">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype='lang-select' key='es-MX'>
<Menu.Item actiontype="lang-select" key="es-MX">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>

View File

@@ -1,43 +1,102 @@
import { Button } from "antd";
import { useMutation } from "@apollo/react-hooks";
import { Button, notification } from "antd";
import axios from "axios";
import React from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export default function JobsCloseExportButton({ jobId, disabled }) {
export function JobsCloseExportButton({ bodyshop, jobId, disabled }) {
const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
const response = await axios.post(
"/accounting/qbxml/receivables",
{ jobId: jobId },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
},
}
);
console.log("handle -> XML", response);
setLoading(true);
let QbXmlResponse;
try {
const response2 = await axios.post(
"http://e9c5a8ed9079.ngrok.io/qb/receivables",
response.data,
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobId: jobId },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
},
}
);
console.log("handle -> result", response2);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("error", error, JSON.stringify(error));
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://e9c5a8ed9079.ngrok.io/qb/receivables",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
});
setLoading(false);
return;
}
console.log("PartnerResponse", PartnerResponse);
const jobUpdateResponse = await updateJob({
variables: {
jobId: jobId,
job: {
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: new Date(),
},
},
});
if (!!!jobUpdateResponse.errors) {
notification["success"]({
message: t("jobs.successes.exported"),
});
} else {
notification["error"]({
message: t("jobs.errors.exporting", {
error: JSON.stringify(jobUpdateResponse.error),
}),
});
}
setLoading(false);
};
return (
<Button onClick={handleQbxml} disabled={disabled} type="dashed">
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
{t("jobs.actions.export")}
</Button>
);
}
export default connect(mapStateToProps, null)(JobsCloseExportButton);

View File

@@ -40,19 +40,6 @@ export function JobsCloseSaveButton({
},
},
},
optimisticResponse: {
update_jobs: {
returning: {
id: jobId,
date_invoiced: new Date(),
status: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*",
invoice_allocation: {
labMatAllocations,
partsAllocations,
},
},
},
},
});
if (!!!result.errors) {
notification["success"]({ message: t("jobs.successes.invoiced") });

View File

@@ -0,0 +1,29 @@
import gql from "graphql-tag";
export const QUERY_JOBS_FOR_EXPORT = gql`
query QUERY_JOBS_FOR_EXPORT($invoicedStatus: String!) {
jobs(
where: {
date_exported: { _is_null: true }
status: { _eq: $invoicedStatus }
}
) {
id
ro_number
ownr_fn
ownr_ln
ownr_co_nm
date_invoiced
date_exported
status
v_model_desc
v_make_desc
v_model_yr
v_color
est_number
clm_total
clm_no
ins_co_nm
}
}
`;

View File

@@ -366,8 +366,8 @@ export const UPDATE_JOB = gql`
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
returning {
id
est_ph1
est_ea
date_exported
status
}
}
}
@@ -824,7 +824,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
rate_mapa
rate_mash
rate_matd
status
status
owner_owing
joblines {
id

View File

@@ -0,0 +1,51 @@
import { useQuery } from "@apollo/react-hooks";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AccountingReceivablesTable from "../../components/accounting-receivables-table/accounting-receivables-table.component";
import AlertComponent from "../../components/alert/alert.component";
import { QUERY_JOBS_FOR_EXPORT } from "../../graphql/accounting.queries";
import { setBreadcrumbs } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
});
export function AccountingReceivablesContainer({ bodyshop, setBreadcrumbs }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.accounting-receivables");
setBreadcrumbs([
{
link: "/manage/accounting/receivables",
label: t("titles.bc.accounting-receivables"),
},
]);
}, [t, setBreadcrumbs]);
const { loading, error, data } = useQuery(QUERY_JOBS_FOR_EXPORT, {
variables: {
invoicedStatus: bodyshop.md_ro_statuses.default_invoiced || "Invoiced*",
},
});
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<div>
<AccountingReceivablesTable
loadaing={loading}
jobs={data ? data.jobs : []}
/>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(AccountingReceivablesContainer);

View File

@@ -91,6 +91,9 @@ const ShopTemplates = lazy(() =>
const JobIntake = lazy(() =>
import("../jobs-intake/jobs-intake.page.container")
);
const AccountingReceivables = lazy(() =>
import("../accounting-receivables/accounting-receivables.container")
);
const AllJobs = lazy(() => import("../jobs-all/jobs-all.container"));
const JobsClose = lazy(() => import("../jobs-close/jobs-close.container"));
@@ -118,8 +121,9 @@ export function Manage({ match, conflict }) {
</Header>
<Layout>
<Content
className='content-container'
style={{ padding: "0em 4em 4em" }}>
className="content-container"
style={{ padding: "0em 4em 4em" }}
>
<FcmNotification />
<ErrorBoundary>
{conflict ? (
@@ -128,7 +132,8 @@ export function Manage({ match, conflict }) {
<Suspense
fallback={
<LoadingSpinner message={t("general.labels.loadingapp")} />
}>
}
>
<BreadCrumbs />
<EnterInvoiceModalContainer />
<EmailOverlayContainer />
@@ -267,6 +272,11 @@ export function Manage({ match, conflict }) {
path={`${match.path}/shop/vendors`}
component={ShopVendorPageContainer}
/>
<Route
exact
path={`${match.path}/accounting/receivables`}
component={AccountingReceivables}
/>
</Suspense>
)}
</ErrorBoundary>

View File

@@ -513,6 +513,8 @@
"addingtoproduction": "Error adding to production. {{error}}",
"creating": "Error encountered while creating job. {{error}}",
"deleted": "Error deleting job.",
"exporting": "Error exporting job. {{error}}",
"exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.",
"invoicing": "Error invoicing job. {{error}}",
"noaccess": "This job does not exist or you do not have access to it.",
"nodamage": "No damage points on estimate.",
@@ -712,6 +714,7 @@
"created_subtitle": "Estimate Number {{est_number}} has been created.",
"creatednoclick": "Job created successfully. ",
"deleted": "Job deleted successfully.",
"exported": "Job exported successfully. ",
"invoiced": "Job closed and invoiced successfully.",
"save": "Job saved successfully.",
"savetitle": "Record saved successfully.",
@@ -726,6 +729,7 @@
},
"header": {
"accounting": "Accounting",
"accounting-receivables": "Receivables",
"activejobs": "Active Jobs",
"alljobs": "All Jobs",
"availablejobs": "Available Jobs",
@@ -946,8 +950,10 @@
}
},
"titles": {
"accounting-receivables": "Receivables | $t(titles.app)",
"app": "ImEX Online",
"bc": {
"accounting-receivables": "Receivables",
"availablejobs": "Available Jobs",
"contracts": "Contracts",
"contracts-create": "New Contract",

View File

@@ -513,6 +513,8 @@
"addingtoproduction": "",
"creating": "",
"deleted": "Error al eliminar el trabajo.",
"exporting": "",
"exporting-partner": "",
"invoicing": "",
"noaccess": "Este trabajo no existe o no tiene acceso a él.",
"nodamage": "",
@@ -712,6 +714,7 @@
"created_subtitle": "",
"creatednoclick": "",
"deleted": "Trabajo eliminado con éxito.",
"exported": "",
"invoiced": "",
"save": "Trabajo guardado con éxito.",
"savetitle": "Registro guardado con éxito.",
@@ -726,6 +729,7 @@
},
"header": {
"accounting": "",
"accounting-receivables": "",
"activejobs": "Empleos activos",
"alljobs": "",
"availablejobs": "Trabajos disponibles",
@@ -946,8 +950,10 @@
}
},
"titles": {
"accounting-receivables": "",
"app": "ImEX Online",
"bc": {
"accounting-receivables": "",
"availablejobs": "",
"contracts": "",
"contracts-create": "",

View File

@@ -513,6 +513,8 @@
"addingtoproduction": "",
"creating": "",
"deleted": "Erreur lors de la suppression du travail.",
"exporting": "",
"exporting-partner": "",
"invoicing": "",
"noaccess": "Ce travail n'existe pas ou vous n'y avez pas accès.",
"nodamage": "",
@@ -712,6 +714,7 @@
"created_subtitle": "",
"creatednoclick": "",
"deleted": "Le travail a bien été supprimé.",
"exported": "",
"invoiced": "",
"save": "Le travail a été enregistré avec succès.",
"savetitle": "Enregistrement enregistré avec succès.",
@@ -726,6 +729,7 @@
},
"header": {
"accounting": "",
"accounting-receivables": "",
"activejobs": "Emplois actifs",
"alljobs": "",
"availablejobs": "Emplois disponibles",
@@ -946,8 +950,10 @@
}
},
"titles": {
"accounting-receivables": "",
"app": "ImEX Online",
"bc": {
"accounting-receivables": "",
"availablejobs": "",
"contracts": "",
"contracts-create": "",