Compare commits
2 Commits
release/20
...
feature/no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5a2be9392 | ||
|
|
df1e15be60 |
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"env": {
|
|
||||||
"es6": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": "eslint:recommended",
|
|
||||||
"globals": {
|
|
||||||
"Atomics": "readonly",
|
|
||||||
"SharedArrayBuffer": "readonly"
|
|
||||||
},
|
|
||||||
|
|
||||||
"parserOptions": {
|
|
||||||
"ecmaVersion": 2018,
|
|
||||||
"sourceType": "module"
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"no-console": "off"
|
|
||||||
},
|
|
||||||
"settings": {},
|
|
||||||
"plugins": ["cypress"]
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -3,55 +3,59 @@
|
|||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
|
"browser": {
|
||||||
|
"fs": false,
|
||||||
|
"path": false,
|
||||||
|
"os": false
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apollo/client": "^3.6.6",
|
"@apollo/client": "^3.5.10",
|
||||||
"@asseinfo/react-kanban": "^2.2.0",
|
"@asseinfo/react-kanban": "^2.2.0",
|
||||||
"@craco/craco": "^6.4.3",
|
"@craco/craco": "^6.4.3",
|
||||||
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
"@fingerprintjs/fingerprintjs": "^3.3.3",
|
||||||
"@sentry/react": "^7.1.1",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@sentry/tracing": "^7.1.1",
|
"@sentry/react": "^6.19.6",
|
||||||
"@splitsoftware/splitio-react": "^1.4.1",
|
"@sentry/tracing": "^6.19.6",
|
||||||
"@stripe/react-stripe-js": "^1.8.1",
|
"@splitsoftware/splitio-react": "^1.4.0",
|
||||||
"@stripe/stripe-js": "^1.31.0",
|
"@stripe/react-stripe-js": "^1.7.1",
|
||||||
"@tanem/react-nprogress": "^5.0.1",
|
"@stripe/stripe-js": "^1.27.0",
|
||||||
"antd": "^4.21.0",
|
"@tanem/react-nprogress": "^5.0.0",
|
||||||
|
"antd": "^4.19.5",
|
||||||
"apollo-link-logger": "^2.0.0",
|
"apollo-link-logger": "^2.0.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.26.1",
|
||||||
"craco-less": "^1.20.0",
|
"craco-less": "^2.0.0",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.0.1",
|
"dotenv": "^16.0.0",
|
||||||
"enquire-js": "^0.2.1",
|
"enquire-js": "^0.2.1",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^9.8.2",
|
"firebase": "^9.6.10",
|
||||||
"graphql": "^16.5.0",
|
"graphql": "^16.3.0",
|
||||||
"i18next": "^21.8.9",
|
"i18next": "^21.6.16",
|
||||||
"i18next-browser-languagedetector": "^6.1.4",
|
"i18next-browser-languagedetector": "^6.1.4",
|
||||||
"jsoneditor": "^9.8.0",
|
"jsoneditor": "^9.7.4",
|
||||||
"jsreport-browser-client-dist": "^1.3.0",
|
"libphonenumber-js": "^1.9.51",
|
||||||
"libphonenumber-js": "^1.10.6",
|
"logrocket": "^2.2.1",
|
||||||
"logrocket": "^3.0.0",
|
"markerjs2": "^2.21.0",
|
||||||
"markerjs2": "^2.21.4",
|
|
||||||
"moment-business-days": "^1.2.0",
|
"moment-business-days": "^1.2.0",
|
||||||
"moment-timezone": "^0.5.34",
|
"moment-timezone": "^0.5.34",
|
||||||
"normalize-url": "^7.0.3",
|
"phone": "^3.1.15",
|
||||||
"phone": "^3.1.20",
|
|
||||||
"preval.macro": "^5.0.0",
|
"preval.macro": "^5.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^7.1.1",
|
"query-string": "^7.1.1",
|
||||||
"rc-queue-anim": "^2.0.0",
|
"rc-queue-anim": "^2.0.0",
|
||||||
"rc-scroll-anim": "^2.7.6",
|
"rc-scroll-anim": "^2.7.6",
|
||||||
"react": "^17.0.2",
|
"react": "^18.0.0",
|
||||||
"react-big-calendar": "^0.40.1",
|
"react-big-calendar": "^0.40.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^4.1.1",
|
"react-cookie": "^4.1.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^18.0.0",
|
||||||
"react-drag-listview": "^0.2.1",
|
"react-drag-listview": "^0.1.9",
|
||||||
"react-grid-gallery": "^0.5.5",
|
"react-grid-gallery": "^0.5.5",
|
||||||
"react-grid-layout": "^1.3.4",
|
"react-grid-layout": "^1.3.4",
|
||||||
"react-i18next": "^11.17.0",
|
"react-i18next": "^11.16.6",
|
||||||
"react-icons": "^4.4.0",
|
"react-icons": "^4.3.1",
|
||||||
"react-number-format": "^4.9.3",
|
"react-number-format": "^4.9.1",
|
||||||
"react-redux": "^7.2.8",
|
"react-redux": "^7.2.8",
|
||||||
"react-resizable": "^3.0.4",
|
"react-resizable": "^3.0.4",
|
||||||
"react-router-dom": "^5.3.0",
|
"react-router-dom": "^5.3.0",
|
||||||
@@ -59,14 +63,14 @@
|
|||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-sublime-video": "^0.2.5",
|
"react-sublime-video": "^0.2.5",
|
||||||
"react-virtualized": "^9.22.3",
|
"react-virtualized": "^9.22.3",
|
||||||
"recharts": "^2.1.10",
|
"recharts": "^2.1.9",
|
||||||
"redux": "^4.2.0",
|
"redux": "^4.1.2",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.1.3",
|
"redux-saga": "^1.1.3",
|
||||||
"redux-state-sync": "^3.1.2",
|
"redux-state-sync": "^3.1.2",
|
||||||
"reselect": "^4.1.6",
|
"reselect": "^4.1.5",
|
||||||
"sass": "^1.51.0",
|
"sass": "^1.50.0",
|
||||||
"socket.io-client": "^4.5.1",
|
"socket.io-client": "^4.4.1",
|
||||||
"styled-components": "^5.3.5",
|
"styled-components": "^5.3.5",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
@@ -118,9 +122,9 @@
|
|||||||
"react-error-overlay": "6.0.9"
|
"react-error-overlay": "6.0.9"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sentry/webpack-plugin": "^1.18.9",
|
"@sentry/webpack-plugin": "^1.18.8",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"cypress": "^9.6.1",
|
"cypress": "^9.5.4",
|
||||||
"eslint-plugin-cypress": "^2.12.1",
|
"eslint-plugin-cypress": "^2.12.1",
|
||||||
"react-error-overlay": "6.0.11",
|
"react-error-overlay": "6.0.11",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 262 KiB |
@@ -13,8 +13,6 @@ import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
|
||||||
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -29,12 +27,7 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(AccountingPayablesTableComponent);
|
)(AccountingPayablesTableComponent);
|
||||||
|
|
||||||
export function AccountingPayablesTableComponent({
|
export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
|
||||||
bodyshop,
|
|
||||||
loading,
|
|
||||||
bills,
|
|
||||||
refetch,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedBills, setSelectedBills] = useState([]);
|
const [selectedBills, setSelectedBills] = useState([]);
|
||||||
const [transInProgress, setTransInProgress] = useState(false);
|
const [transInProgress, setTransInProgress] = useState(false);
|
||||||
@@ -138,9 +131,11 @@ export function AccountingPayablesTableComponent({
|
|||||||
dataIndex: "attempts",
|
dataIndex: "attempts",
|
||||||
key: "attempts",
|
key: "attempts",
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => {
|
||||||
<ExportLogsCountDisplay logs={record.exportlogs} />
|
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||||
),
|
const attempts = record.exportlogs.length;
|
||||||
|
return `${success}/${attempts}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
@@ -149,13 +144,14 @@ export function AccountingPayablesTableComponent({
|
|||||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<PayableExportButton
|
<div>
|
||||||
billId={record.id}
|
<PayableExportButton
|
||||||
disabled={transInProgress || !!record.exported}
|
billId={record.id}
|
||||||
loadingCallback={setTransInProgress}
|
disabled={transInProgress || !!record.exported}
|
||||||
setSelectedBills={setSelectedBills}
|
loadingCallback={setTransInProgress}
|
||||||
refetch={refetch}
|
setSelectedBills={setSelectedBills}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -181,19 +177,11 @@ export function AccountingPayablesTableComponent({
|
|||||||
<Card
|
<Card
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<BillMarkSelectedExported
|
|
||||||
billids={selectedBills}
|
|
||||||
disabled={transInProgress || selectedBills.length === 0}
|
|
||||||
loadingCallback={setTransInProgress}
|
|
||||||
completedCallback={setSelectedBills}
|
|
||||||
refetch={refetch}
|
|
||||||
/>
|
|
||||||
<PayableExportAll
|
<PayableExportAll
|
||||||
billids={selectedBills}
|
billids={selectedBills}
|
||||||
disabled={transInProgress || selectedBills.length === 0}
|
disabled={transInProgress || selectedBills.length === 0}
|
||||||
loadingCallback={setTransInProgress}
|
loadingCallback={setTransInProgress}
|
||||||
completedCallback={setSelectedBills}
|
completedCallback={setSelectedBills}
|
||||||
refetch={refetch}
|
|
||||||
/>
|
/>
|
||||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||||
<QboAuthorizeComponent />
|
<QboAuthorizeComponent />
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -32,7 +31,6 @@ export function AccountingPayablesTableComponent({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
loading,
|
loading,
|
||||||
payments,
|
payments,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedPayments, setSelectedPayments] = useState([]);
|
const [selectedPayments, setSelectedPayments] = useState([]);
|
||||||
@@ -132,9 +130,11 @@ export function AccountingPayablesTableComponent({
|
|||||||
dataIndex: "attempts",
|
dataIndex: "attempts",
|
||||||
key: "attempts",
|
key: "attempts",
|
||||||
|
|
||||||
render: (text, record) => (
|
render: (text, record) => {
|
||||||
<ExportLogsCountDisplay logs={record.exportlogs} />
|
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||||
),
|
const attempts = record.exportlogs.length;
|
||||||
|
return `${success}/${attempts}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
@@ -148,7 +148,6 @@ export function AccountingPayablesTableComponent({
|
|||||||
disabled={transInProgress || !!record.exportedat}
|
disabled={transInProgress || !!record.exportedat}
|
||||||
loadingCallback={setTransInProgress}
|
loadingCallback={setTransInProgress}
|
||||||
setSelectedPayments={setSelectedPayments}
|
setSelectedPayments={setSelectedPayments}
|
||||||
refetch={refetch}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -189,7 +188,6 @@ export function AccountingPayablesTableComponent({
|
|||||||
disabled={transInProgress || selectedPayments.length === 0}
|
disabled={transInProgress || selectedPayments.length === 0}
|
||||||
loadingCallback={setTransInProgress}
|
loadingCallback={setTransInProgress}
|
||||||
completedCallback={setSelectedPayments}
|
completedCallback={setSelectedPayments}
|
||||||
refetch={refetch}
|
|
||||||
/>
|
/>
|
||||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||||
<QboAuthorizeComponent />
|
<QboAuthorizeComponent />
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -31,7 +30,6 @@ export function AccountingReceivablesTableComponent({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
loading,
|
loading,
|
||||||
jobs,
|
jobs,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedJobs, setSelectedJobs] = useState([]);
|
const [selectedJobs, setSelectedJobs] = useState([]);
|
||||||
@@ -141,9 +139,12 @@ export function AccountingReceivablesTableComponent({
|
|||||||
title: t("exportlogs.labels.attempts"),
|
title: t("exportlogs.labels.attempts"),
|
||||||
dataIndex: "attempts",
|
dataIndex: "attempts",
|
||||||
key: "attempts",
|
key: "attempts",
|
||||||
render: (text, record) => (
|
|
||||||
<ExportLogsCountDisplay logs={record.exportlogs} />
|
render: (text, record) => {
|
||||||
),
|
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||||
|
const attempts = record.exportlogs.length;
|
||||||
|
return `${success}/${attempts}`;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
@@ -156,7 +157,6 @@ export function AccountingReceivablesTableComponent({
|
|||||||
jobId={record.id}
|
jobId={record.id}
|
||||||
disabled={!!record.date_exported}
|
disabled={!!record.date_exported}
|
||||||
setSelectedJobs={setSelectedJobs}
|
setSelectedJobs={setSelectedJobs}
|
||||||
refetch={refetch}
|
|
||||||
/>
|
/>
|
||||||
<Link to={`/manage/jobs/${record.id}/close`}>
|
<Link to={`/manage/jobs/${record.id}/close`}>
|
||||||
<Button>{t("jobs.labels.viewallocations")}</Button>
|
<Button>{t("jobs.labels.viewallocations")}</Button>
|
||||||
@@ -207,7 +207,6 @@ export function AccountingReceivablesTableComponent({
|
|||||||
disabled={transInProgress || selectedJobs.length === 0}
|
disabled={transInProgress || selectedJobs.length === 0}
|
||||||
loadingCallback={setTransInProgress}
|
loadingCallback={setTransInProgress}
|
||||||
completedCallback={setSelectedJobs}
|
completedCallback={setSelectedJobs}
|
||||||
refetch={refetch}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
|
||||||
import "./bill-cm-returns-table.styles.scss";
|
|
||||||
export default function BillCmdReturnsTableComponent({
|
|
||||||
form,
|
|
||||||
returnLoading,
|
|
||||||
returnData,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (returnData) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
outstanding_returns: returnData.parts_order_lines,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [returnData, form]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
shouldUpdate={(prev, cur) =>
|
|
||||||
prev.jobid !== cur.jobid ||
|
|
||||||
prev.is_credit_memo !== cur.is_credit_memo ||
|
|
||||||
prev.vendorid !== cur.vendorid
|
|
||||||
}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
{() => {
|
|
||||||
const isReturn = form.getFieldValue("is_credit_memo");
|
|
||||||
|
|
||||||
if (!isReturn) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (returnLoading) return <Skeleton />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.List name="outstanding_returns">
|
|
||||||
{(fields, { add, remove, move }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography.Title level={4}>
|
|
||||||
{t("bills.labels.creditsnotreceived")}
|
|
||||||
</Typography.Title>
|
|
||||||
<table className="bill-cm-returns-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{t("parts_orders.fields.line_desc")}</th>
|
|
||||||
<th>{t("parts_orders.fields.part_type")}</th>
|
|
||||||
<th>{t("parts_orders.fields.quantity")}</th>
|
|
||||||
<th>{t("parts_orders.fields.act_price")}</th>
|
|
||||||
<th>{t("parts_orders.fields.cost")}</th>
|
|
||||||
<th>{t("parts_orders.labels.mark_as_received")}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<tr key={field.key}>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
// label={t("joblines.fields.line_desc")}
|
|
||||||
key={`${index}line_desc`}
|
|
||||||
name={[field.name, "line_desc"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}part_type`}
|
|
||||||
name={[field.name, "part_type"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}quantity`}
|
|
||||||
name={[field.name, "quantity"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}act_price`}
|
|
||||||
name={[field.name, "act_price"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent type="currency" />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}cost`}
|
|
||||||
name={[field.name, "cost"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent type="currency" />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}cm_received`}
|
|
||||||
name={[field.name, "cm_received"]}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Checkbox />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.List>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
.bill-cm-returns-table {
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 8px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
|
|
||||||
.ant-form-item {
|
|
||||||
margin-bottom: 0px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,8 +15,7 @@ export default function BillDeleteButton({ bill }) {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await deleteBill({
|
const result = await deleteBill({
|
||||||
variables: { billId: bill.id },
|
variables: { billId: bill.id },
|
||||||
update(cache, { errors }) {
|
update(cache) {
|
||||||
if (errors) return;
|
|
||||||
cache.modify({
|
cache.modify({
|
||||||
fields: {
|
fields: {
|
||||||
bills(existingBills, { readField }) {
|
bills(existingBills, { readField }) {
|
||||||
@@ -37,22 +36,11 @@ export default function BillDeleteButton({ bill }) {
|
|||||||
if (!!!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({ message: t("bills.successes.deleted") });
|
notification["success"]({ message: t("bills.successes.deleted") });
|
||||||
} else {
|
} else {
|
||||||
//Check if it's an fkey violation.
|
notification["error"]({
|
||||||
const error = JSON.stringify(result.errors);
|
message: t("bills.errors.deleting", {
|
||||||
|
error: JSON.stringify(result.errors),
|
||||||
if (error.toLowerCase().includes("inventory_billid_fkey")) {
|
}),
|
||||||
notification["error"]({
|
});
|
||||||
message: t("bills.errors.deleting", {
|
|
||||||
error: t("bills.errors.existinginventoryline"),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bills.errors.deleting", {
|
|
||||||
error: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -12,29 +12,27 @@ import moment from "moment";
|
|||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { useLocation, useHistory } from "react-router-dom";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import {
|
import {
|
||||||
DELETE_BILL_LINE,
|
DELETE_BILL_LINE,
|
||||||
INSERT_NEW_BILL_LINES,
|
INSERT_NEW_BILL_LINES,
|
||||||
UPDATE_BILL_LINE,
|
UPDATE_BILL_LINE,
|
||||||
} from "../../graphql/bill-lines.queries";
|
} from "../../graphql/bill-lines.queries";
|
||||||
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
|
||||||
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
|
||||||
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
|
||||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
|
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
@@ -51,7 +49,6 @@ export default connect(
|
|||||||
export function BillDetailEditcontainer({
|
export function BillDetailEditcontainer({
|
||||||
setPartsOrderContext,
|
setPartsOrderContext,
|
||||||
insertAuditTrail,
|
insertAuditTrail,
|
||||||
bodyshop,
|
|
||||||
}) {
|
}) {
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -73,8 +70,8 @@ export function BillDetailEditcontainer({
|
|||||||
sm: "100%",
|
sm: "100%",
|
||||||
md: "100%",
|
md: "100%",
|
||||||
lg: "100%",
|
lg: "100%",
|
||||||
xl: "90%",
|
xl: "80%",
|
||||||
xxl: "90%",
|
xxl: "80%",
|
||||||
};
|
};
|
||||||
const drawerPercentage = selectedBreakpoint
|
const drawerPercentage = selectedBreakpoint
|
||||||
? bpoints[selectedBreakpoint[0]]
|
? bpoints[selectedBreakpoint[0]]
|
||||||
@@ -127,7 +124,7 @@ export function BillDetailEditcontainer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
billlines.forEach((billline) => {
|
billlines.forEach((billline) => {
|
||||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
const { deductedfromlbr, jobline, ...il } = billline;
|
||||||
delete il.__typename;
|
delete il.__typename;
|
||||||
|
|
||||||
if (il.id) {
|
if (il.id) {
|
||||||
@@ -268,21 +265,12 @@ export function BillDetailEditcontainer({
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
>
|
>
|
||||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
<BillFormContainer form={form} billEdit disabled={exported} />
|
||||||
|
<JobDocumentsGallery
|
||||||
{bodyshop.uselocalmediaserver ? (
|
jobId={data ? data.bills_by_pk.jobid : null}
|
||||||
<JobsDocumentsLocalGallery
|
billId={search.billid}
|
||||||
job={{ id: data ? data.bills_by_pk.jobid : null }}
|
documentsList={data ? data.bills_by_pk.documents : []}
|
||||||
invoice_number={data ? data.bills_by_pk.invoice_number : null}
|
billsCallback={refetch}
|
||||||
vendorid={data ? data.bills_by_pk.vendorid : null}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<JobDocumentsGallery
|
|
||||||
jobId={data ? data.bills_by_pk.jobid : null}
|
|
||||||
billId={search.billid}
|
|
||||||
documentsList={data ? data.bills_by_pk.documents : []}
|
|
||||||
billsCallback={refetch}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Form>
|
</Form>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import {
|
|||||||
QUERY_JOB_LBR_ADJUSTMENTS,
|
QUERY_JOB_LBR_ADJUSTMENTS,
|
||||||
UPDATE_JOB,
|
UPDATE_JOB,
|
||||||
} from "../../graphql/jobs.queries";
|
} from "../../graphql/jobs.queries";
|
||||||
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
|
|
||||||
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
|
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
||||||
@@ -25,7 +23,6 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
||||||
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
import { handleUpload } from "../documents-upload/documents-upload.utility";
|
||||||
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
billEnterModal: selectBillEnterModal,
|
billEnterModal: selectBillEnterModal,
|
||||||
@@ -50,8 +47,6 @@ function BillEnterModalContainer({
|
|||||||
const [enterAgain, setEnterAgain] = useState(false);
|
const [enterAgain, setEnterAgain] = useState(false);
|
||||||
const [insertBill] = useMutation(INSERT_NEW_BILL);
|
const [insertBill] = useMutation(INSERT_NEW_BILL);
|
||||||
const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
|
const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
|
||||||
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
|
|
||||||
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
@@ -81,13 +76,7 @@ function BillEnterModalContainer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const {
|
const { upload, location, ...remainingValues } = values;
|
||||||
upload,
|
|
||||||
location,
|
|
||||||
outstanding_returns,
|
|
||||||
inventory,
|
|
||||||
...remainingValues
|
|
||||||
} = values;
|
|
||||||
|
|
||||||
let adjustmentsToInsert = {};
|
let adjustmentsToInsert = {};
|
||||||
|
|
||||||
@@ -167,25 +156,6 @@ function BillEnterModalContainer({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const markPolReceived =
|
|
||||||
outstanding_returns &&
|
|
||||||
outstanding_returns.filter((o) => o.cm_received === true);
|
|
||||||
|
|
||||||
if (markPolReceived && markPolReceived.length > 0) {
|
|
||||||
const r2 = await updatePartsOrderLines({
|
|
||||||
variables: { partsLineIds: markPolReceived.map((p) => p.id) },
|
|
||||||
});
|
|
||||||
if (!!r2.errors) {
|
|
||||||
setLoading(false);
|
|
||||||
setEnterAgain(false);
|
|
||||||
notification["error"]({
|
|
||||||
message: t("parts_orders.errors.updating", {
|
|
||||||
message: JSON.stringify(r2.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!!r1.errors) {
|
if (!!r1.errors) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setEnterAgain(false);
|
setEnterAgain(false);
|
||||||
@@ -197,26 +167,6 @@ function BillEnterModalContainer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const billId = r1.data.insert_bills.returning[0].id;
|
const billId = r1.data.insert_bills.returning[0].id;
|
||||||
const markInventoryConsumed =
|
|
||||||
inventory && inventory.filter((i) => i.consumefrominventory);
|
|
||||||
|
|
||||||
if (markInventoryConsumed && markInventoryConsumed.length > 0) {
|
|
||||||
const r2 = await updateInventoryLines({
|
|
||||||
variables: {
|
|
||||||
InventoryIds: markInventoryConsumed.map((p) => p.id),
|
|
||||||
consumedbybillid: billId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
if (!!r2.errors) {
|
|
||||||
setLoading(false);
|
|
||||||
setEnterAgain(false);
|
|
||||||
notification["error"]({
|
|
||||||
message: t("inventory.errors.updating", {
|
|
||||||
message: JSON.stringify(r2.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
remainingValues.billlines
|
remainingValues.billlines
|
||||||
@@ -238,33 +188,19 @@ function BillEnterModalContainer({
|
|||||||
/////////////////////////
|
/////////////////////////
|
||||||
if (upload && upload.length > 0) {
|
if (upload && upload.length > 0) {
|
||||||
//insert Each of the documents?
|
//insert Each of the documents?
|
||||||
|
upload.forEach((u) => {
|
||||||
if (bodyshop.uselocalmediaserver) {
|
handleUpload(
|
||||||
upload.forEach((u) => {
|
{ file: u.originFileObj },
|
||||||
handleLocalUpload({
|
{
|
||||||
ev: { file: u.originFileObj },
|
bodyshop: bodyshop,
|
||||||
context: {
|
uploaded_by: currentUser.email,
|
||||||
jobid: values.jobid,
|
jobId: values.jobid,
|
||||||
invoice_number: remainingValues.invoice_number,
|
billId: billId,
|
||||||
vendorid: remainingValues.vendorid,
|
tagsArray: null,
|
||||||
},
|
callback: null,
|
||||||
});
|
}
|
||||||
});
|
);
|
||||||
} else {
|
});
|
||||||
upload.forEach((u) => {
|
|
||||||
handleUpload(
|
|
||||||
{ file: u.originFileObj },
|
|
||||||
{
|
|
||||||
bodyshop: bodyshop,
|
|
||||||
uploaded_by: currentUser.email,
|
|
||||||
jobId: values.jobid,
|
|
||||||
billId: billId,
|
|
||||||
tagsArray: null,
|
|
||||||
callback: null,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
///////////////////////////
|
///////////////////////////
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ export function BillFormComponent({
|
|||||||
billEdit,
|
billEdit,
|
||||||
disableInvNumber,
|
disableInvNumber,
|
||||||
job,
|
job,
|
||||||
loadOutstandingReturns,
|
|
||||||
loadInventory,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -58,18 +56,8 @@ export function BillFormComponent({
|
|||||||
{},
|
{},
|
||||||
bodyshop.imexshopid
|
bodyshop.imexshopid
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleVendorSelect = (props, opt) => {
|
const handleVendorSelect = (props, opt) => {
|
||||||
setDiscount(opt.discount);
|
setDiscount(opt.discount);
|
||||||
|
|
||||||
opt &&
|
|
||||||
!billEdit &&
|
|
||||||
loadOutstandingReturns({
|
|
||||||
variables: {
|
|
||||||
jobId: form.getFieldValue("jobid"),
|
|
||||||
vendorId: opt.value,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,8 +65,8 @@ export function BillFormComponent({
|
|||||||
}, [job, form]);
|
}, [job, form]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const vendorId = form.getFieldValue("vendorid");
|
if (form.getFieldValue("vendorid") && vendorAutoCompleteOptions) {
|
||||||
if (vendorId && vendorAutoCompleteOptions) {
|
const vendorId = form.getFieldValue("vendorid");
|
||||||
const matchingVendors = vendorAutoCompleteOptions.filter(
|
const matchingVendors = vendorAutoCompleteOptions.filter(
|
||||||
(v) => v.id === vendorId
|
(v) => v.id === vendorId
|
||||||
);
|
);
|
||||||
@@ -86,32 +74,10 @@ export function BillFormComponent({
|
|||||||
setDiscount(matchingVendors[0].discount);
|
setDiscount(matchingVendors[0].discount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const jobId = form.getFieldValue("jobid");
|
if (form.getFieldValue("jobid")) {
|
||||||
if (jobId) {
|
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
||||||
loadLines({ variables: { id: jobId } });
|
|
||||||
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
|
|
||||||
loadOutstandingReturns({
|
|
||||||
variables: {
|
|
||||||
jobId: jobId,
|
|
||||||
vendorId: vendorId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, [form, setDiscount, vendorAutoCompleteOptions, loadLines]);
|
||||||
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
|
|
||||||
loadInventory();
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
form,
|
|
||||||
billEdit,
|
|
||||||
loadOutstandingReturns,
|
|
||||||
loadInventory,
|
|
||||||
setDiscount,
|
|
||||||
vendorAutoCompleteOptions,
|
|
||||||
loadLines,
|
|
||||||
bodyshop.inhousevendorid,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -141,14 +107,6 @@ export function BillFormComponent({
|
|||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (form.getFieldValue("jobid") !== null) {
|
if (form.getFieldValue("jobid") !== null) {
|
||||||
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
loadLines({ variables: { id: form.getFieldValue("jobid") } });
|
||||||
if (form.getFieldValue("vendorid") !== null) {
|
|
||||||
loadOutstandingReturns({
|
|
||||||
variables: {
|
|
||||||
jobId: form.getFieldValue("jobid"),
|
|
||||||
vendorId: form.getFieldValue("vendorid"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -270,23 +228,8 @@ export function BillFormComponent({
|
|||||||
rules={[
|
rules={[
|
||||||
({ getFieldValue }) => ({
|
({ getFieldValue }) => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
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: {
|
|
||||||
// jobId: form.getFieldValue("jobid"),
|
|
||||||
// vendorId: form.getFieldValue("vendorid"),
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!bodyshop.bill_allow_post_to_closed &&
|
!bodyshop.bill_allow_post_to_closed &&
|
||||||
job &&
|
|
||||||
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
|
||||||
job.status === bodyshop.md_ro_statuses.default_exported ||
|
job.status === bodyshop.md_ro_statuses.default_exported ||
|
||||||
job.status === bodyshop.md_ro_statuses.default_void) &&
|
job.status === bodyshop.md_ro_statuses.default_void) &&
|
||||||
@@ -437,7 +380,6 @@ export function BillFormComponent({
|
|||||||
form={form}
|
form={form}
|
||||||
responsibilityCenters={responsibilityCenters}
|
responsibilityCenters={responsibilityCenters}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
billEdit={billEdit}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,6 @@ import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
|
|||||||
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
|
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import BillFormComponent from "./bill-form.component";
|
import BillFormComponent from "./bill-form.component";
|
||||||
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
|
|
||||||
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
|
|
||||||
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
|
|
||||||
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
|
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -23,12 +18,6 @@ export function BillFormContainer({
|
|||||||
disabled,
|
disabled,
|
||||||
disableInvNumber,
|
disableInvNumber,
|
||||||
}) {
|
}) {
|
||||||
const { Simple_Inventory } = useTreatments(
|
|
||||||
["Simple_Inventory"],
|
|
||||||
{},
|
|
||||||
bodyshop && bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: VendorAutoCompleteData } = useQuery(
|
const { data: VendorAutoCompleteData } = useQuery(
|
||||||
SEARCH_VENDOR_AUTOCOMPLETE,
|
SEARCH_VENDOR_AUTOCOMPLETE,
|
||||||
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
|
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
|
||||||
@@ -38,44 +27,20 @@ export function BillFormContainer({
|
|||||||
GET_JOB_LINES_TO_ENTER_BILL
|
GET_JOB_LINES_TO_ENTER_BILL
|
||||||
);
|
);
|
||||||
|
|
||||||
const [loadOutstandingReturns, { loading: returnLoading, data: returnData }] =
|
|
||||||
useLazyQuery(QUERY_UNRECEIVED_LINES);
|
|
||||||
const [loadInventory, { loading: inventoryLoading, data: inventoryData }] =
|
|
||||||
useLazyQuery(QUERY_OUTSTANDING_INVENTORY);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<BillFormComponent
|
||||||
<BillFormComponent
|
disabled={disabled}
|
||||||
disabled={disabled}
|
form={form}
|
||||||
form={form}
|
billEdit={billEdit}
|
||||||
billEdit={billEdit}
|
vendorAutoCompleteOptions={
|
||||||
vendorAutoCompleteOptions={
|
VendorAutoCompleteData && VendorAutoCompleteData.vendors
|
||||||
VendorAutoCompleteData && VendorAutoCompleteData.vendors
|
}
|
||||||
}
|
loadLines={loadLines}
|
||||||
loadLines={loadLines}
|
lineData={lineData ? lineData.joblines : []}
|
||||||
lineData={lineData ? lineData.joblines : []}
|
job={lineData ? lineData.jobs_by_pk : null}
|
||||||
job={lineData ? lineData.jobs_by_pk : null}
|
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
||||||
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
disableInvNumber={disableInvNumber}
|
||||||
disableInvNumber={disableInvNumber}
|
/>
|
||||||
loadOutstandingReturns={loadOutstandingReturns}
|
|
||||||
loadInventory={loadInventory}
|
|
||||||
/>
|
|
||||||
{!billEdit && (
|
|
||||||
<BillCmdReturnsTableComponent
|
|
||||||
form={form}
|
|
||||||
returnLoading={returnLoading}
|
|
||||||
returnData={returnData}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{Simple_Inventory.treatment === "on" && (
|
|
||||||
<BillInventoryTable
|
|
||||||
form={form}
|
|
||||||
inventoryLoading={inventoryLoading}
|
|
||||||
inventoryData={billEdit ? [] : inventoryData}
|
|
||||||
billEdit={billEdit}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(mapStateToProps, null)(BillFormContainer);
|
export default connect(mapStateToProps, null)(BillFormContainer);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
Tooltip,
|
Tooltip
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -18,8 +18,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import CiecaSelect from "../../utils/Ciecaselect";
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
|
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -36,16 +34,10 @@ export function BillEnterModalLinesComponent({
|
|||||||
discount,
|
discount,
|
||||||
form,
|
form,
|
||||||
responsibilityCenters,
|
responsibilityCenters,
|
||||||
billEdit,
|
|
||||||
billid,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
|
||||||
const { Simple_Inventory } = useTreatments(
|
|
||||||
["Simple_Inventory"],
|
|
||||||
{},
|
|
||||||
bodyshop && bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
const columns = (remove) => {
|
const columns = (remove) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -150,24 +142,6 @@ export function BillEnterModalLinesComponent({
|
|||||||
required: true,
|
required: true,
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
if (
|
|
||||||
value &&
|
|
||||||
getFieldValue("billlines")[field.fieldKey]?.inventories
|
|
||||||
?.length > value
|
|
||||||
) {
|
|
||||||
return Promise.reject(
|
|
||||||
t("bills.validation.inventoryquantity", {
|
|
||||||
number:
|
|
||||||
getFieldValue("billlines")[field.fieldKey]
|
|
||||||
?.inventories?.length,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -503,33 +477,9 @@ export function BillEnterModalLinesComponent({
|
|||||||
|
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Form.Item shouldUpdate noStyle>
|
<Button disabled={disabled} onClick={() => remove(record.name)}>
|
||||||
{() => (
|
<DeleteFilled />
|
||||||
<Space wrap>
|
</Button>
|
||||||
<Button
|
|
||||||
disabled={
|
|
||||||
disabled ||
|
|
||||||
getFieldValue("billlines")[record.fieldKey]?.inventories
|
|
||||||
?.length > 0
|
|
||||||
}
|
|
||||||
onClick={() => remove(record.name)}
|
|
||||||
>
|
|
||||||
<DeleteFilled />
|
|
||||||
</Button>
|
|
||||||
{Simple_Inventory.treatment === "on" && (
|
|
||||||
<BilllineAddInventory
|
|
||||||
disabled={
|
|
||||||
!billEdit ||
|
|
||||||
form.isFieldsTouched() ||
|
|
||||||
form.getFieldValue("is_credit_memo")
|
|
||||||
}
|
|
||||||
billline={getFieldValue("billlines")[record.fieldKey]}
|
|
||||||
jobid={getFieldValue("jobid")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,173 +0,0 @@
|
|||||||
import { Checkbox, Form, Skeleton, Typography } from "antd";
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
|
|
||||||
import "./bill-inventory-table.styles.scss";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
billEnterModal: selectBillEnterModal,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
|
|
||||||
|
|
||||||
export function BillInventoryTable({
|
|
||||||
billEnterModal,
|
|
||||||
bodyshop,
|
|
||||||
form,
|
|
||||||
billEdit,
|
|
||||||
inventoryLoading,
|
|
||||||
inventoryData,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (inventoryData && inventoryData.inventory) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
inventory: billEnterModal.context.consumeinventoryid
|
|
||||||
? inventoryData.inventory.map((i) => {
|
|
||||||
if (i.id === billEnterModal.context.consumeinventoryid)
|
|
||||||
i.consumefrominventory = true;
|
|
||||||
return i;
|
|
||||||
})
|
|
||||||
: inventoryData.inventory,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [inventoryData, form, billEnterModal.context.consumeinventoryid]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
shouldUpdate={(prev, cur) => prev.vendorid !== cur.vendorid}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
{() => {
|
|
||||||
const is_inhouse =
|
|
||||||
form.getFieldValue("vendorid") === bodyshop.inhousevendorid;
|
|
||||||
|
|
||||||
if (!is_inhouse || billEdit) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inventoryLoading) return <Skeleton />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.List name="inventory">
|
|
||||||
{(fields, { add, remove, move }) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Typography.Title level={4}>
|
|
||||||
{t("inventory.labels.inventory")}
|
|
||||||
</Typography.Title>
|
|
||||||
<table className="bill-inventory-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{t("billlines.fields.line_desc")}</th>
|
|
||||||
<th>{t("vendors.fields.name")}</th>
|
|
||||||
<th>{t("billlines.fields.quantity")}</th>
|
|
||||||
<th>{t("billlines.fields.actual_price")}</th>
|
|
||||||
<th>{t("billlines.fields.actual_cost")}</th>
|
|
||||||
<th>{t("inventory.fields.comment")}</th>
|
|
||||||
<th>{t("inventory.actions.consumefrominventory")}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{fields.map((field, index) => (
|
|
||||||
<tr key={field.key}>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
// label={t("joblines.fields.line_desc")}
|
|
||||||
key={`${index}line_desc`}
|
|
||||||
name={[field.name, "line_desc"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}part_type`}
|
|
||||||
name={[
|
|
||||||
field.name,
|
|
||||||
"billline",
|
|
||||||
"bill",
|
|
||||||
"vendor",
|
|
||||||
"name",
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}quantity`}
|
|
||||||
name={[field.name, "quantity"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}act_price`}
|
|
||||||
name={[field.name, "actual_price"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent type="currency" />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}cost`}
|
|
||||||
name={[field.name, "actual_cost"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent type="currency" />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}comment`}
|
|
||||||
name={[field.name, "comment"]}
|
|
||||||
>
|
|
||||||
<ReadOnlyFormItemComponent />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>
|
|
||||||
<Form.Item
|
|
||||||
span={2}
|
|
||||||
//label={t("joblines.fields.mod_lb_hrs")}
|
|
||||||
key={`${index}consumefrominventory`}
|
|
||||||
name={[field.name, "consumefrominventory"]}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Checkbox />
|
|
||||||
</Form.Item>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.List>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
.bill-inventory-table {
|
|
||||||
table-layout: fixed;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
th,
|
|
||||||
td {
|
|
||||||
padding: 8px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
|
|
||||||
.ant-form-item {
|
|
||||||
margin-bottom: 0px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:hover {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,14 +9,11 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import {
|
import {
|
||||||
selectAuthLevel,
|
selectAuthLevel,
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
authLevel: selectAuthLevel,
|
authLevel: selectAuthLevel,
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
@@ -27,15 +24,9 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(BillMarkExportedButton);
|
)(BillMarkExportedButton);
|
||||||
|
|
||||||
export function BillMarkExportedButton({
|
export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
|
||||||
currentUser,
|
|
||||||
bodyshop,
|
|
||||||
authLevel,
|
|
||||||
bill,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
|
||||||
|
|
||||||
const [updateBill] = useMutation(gql`
|
const [updateBill] = useMutation(gql`
|
||||||
mutation UPDATE_BILL($billId: uuid!) {
|
mutation UPDATE_BILL($billId: uuid!) {
|
||||||
@@ -55,20 +46,6 @@ export function BillMarkExportedButton({
|
|||||||
variables: { billId: bill.id },
|
variables: { billId: bill.id },
|
||||||
});
|
});
|
||||||
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
billid: bill.id,
|
|
||||||
successful: true,
|
|
||||||
message: JSON.stringify([t("general.labels.markedexported")]),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("bills.successes.markexported"),
|
message: t("bills.successes.markexported"),
|
||||||
@@ -92,7 +69,11 @@ export function BillMarkExportedButton({
|
|||||||
|
|
||||||
if (hasAccess)
|
if (hasAccess)
|
||||||
return (
|
return (
|
||||||
<Button loading={loading} disabled={bill.exported} onClick={handleUpdate}>
|
<Button
|
||||||
|
loading={loading}
|
||||||
|
disabled={bill.exported}
|
||||||
|
onClick={handleUpdate}
|
||||||
|
>
|
||||||
{t("bills.labels.markexported")}
|
{t("bills.labels.markexported")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,155 +0,0 @@
|
|||||||
import { FileAddFilled } from "@ant-design/icons";
|
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Button, notification, Tooltip } from "antd";
|
|
||||||
import { t } from "i18next";
|
|
||||||
import moment from "moment";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
|
|
||||||
import {
|
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(BilllineAddInventory);
|
|
||||||
|
|
||||||
export function BilllineAddInventory({
|
|
||||||
currentUser,
|
|
||||||
bodyshop,
|
|
||||||
billline,
|
|
||||||
disabled,
|
|
||||||
jobid,
|
|
||||||
}) {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { billid } = queryString.parse(useLocation().search);
|
|
||||||
|
|
||||||
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
|
|
||||||
|
|
||||||
const addToInventory = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
//Check to make sure there are no existing items already in the inventory.
|
|
||||||
|
|
||||||
const cm = {
|
|
||||||
vendorid: bodyshop.inhousevendorid,
|
|
||||||
invoice_number: "ih",
|
|
||||||
jobid: jobid,
|
|
||||||
isinhouse: true,
|
|
||||||
is_credit_memo: true,
|
|
||||||
date: moment().format("YYYY-MM-DD"),
|
|
||||||
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
|
|
||||||
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
|
|
||||||
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
|
|
||||||
total: 0,
|
|
||||||
billlines: [
|
|
||||||
{
|
|
||||||
actual_price: billline.actual_price,
|
|
||||||
actual_cost: billline.actual_cost,
|
|
||||||
quantity: billline.quantity,
|
|
||||||
line_desc: billline.line_desc,
|
|
||||||
cost_center: billline.cost_center,
|
|
||||||
deductedfromlbr: billline.deductedfromlbr,
|
|
||||||
applicable_taxes: {
|
|
||||||
local: billline.applicable_taxes.local,
|
|
||||||
state: billline.applicable_taxes.state,
|
|
||||||
federal: billline.applicable_taxes.federal,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100;
|
|
||||||
|
|
||||||
const insertResult = await insertInventoryLine({
|
|
||||||
variables: {
|
|
||||||
joblineId:
|
|
||||||
billline.joblineid === "noline" ? billline.id : billline.joblineid, //This will return null as there will be no jobline that has the id of the bill line.
|
|
||||||
//Unfortunately, we can't send null as the GQL syntax validation fails.
|
|
||||||
joblineStatus: bodyshop.md_order_statuses.default_returned,
|
|
||||||
inv: {
|
|
||||||
shopid: bodyshop.id,
|
|
||||||
billlineid: billline.id,
|
|
||||||
actual_price: billline.actual_price,
|
|
||||||
actual_cost: billline.actual_cost,
|
|
||||||
quantity: billline.quantity,
|
|
||||||
line_desc: billline.line_desc,
|
|
||||||
},
|
|
||||||
cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
|
|
||||||
pol: {
|
|
||||||
returnfrombill: billid,
|
|
||||||
vendorid: bodyshop.inhousevendorid,
|
|
||||||
deliver_by: moment().format("YYYY-MM-DD"),
|
|
||||||
parts_order_lines: {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
line_desc: billline.line_desc,
|
|
||||||
|
|
||||||
act_price: billline.actual_price,
|
|
||||||
cost: billline.actual_cost,
|
|
||||||
quantity: billline.quantity,
|
|
||||||
job_line_id:
|
|
||||||
billline.joblineid === "noline" ? null : billline.joblineid,
|
|
||||||
part_type: billline.jobline && billline.jobline.part_type,
|
|
||||||
cm_received: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
order_date: "2022-06-01",
|
|
||||||
orderedby: currentUser.email,
|
|
||||||
jobid: jobid,
|
|
||||||
user_email: currentUser.email,
|
|
||||||
return: true,
|
|
||||||
status: "Ordered",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
refetchQueries: ["QUERY_BILL_BY_PK"],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!insertResult.errors) {
|
|
||||||
notification.open({
|
|
||||||
type: "success",
|
|
||||||
message: t("inventory.successes.inserted"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification.open({
|
|
||||||
type: "error",
|
|
||||||
message: t("inventory.errors.inserting", {
|
|
||||||
error: JSON.stringify(insertResult.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tooltip title={t("inventory.actions.addtoinventory")}>
|
|
||||||
<Button
|
|
||||||
loading={loading}
|
|
||||||
disabled={
|
|
||||||
disabled || billline?.inventories?.length >= billline.quantity
|
|
||||||
}
|
|
||||||
onClick={addToInventory}
|
|
||||||
>
|
|
||||||
<FileAddFilled />
|
|
||||||
{billline?.inventories?.length > 0 && (
|
|
||||||
<div>({billline?.inventories?.length} in inv)</div>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
@@ -15,7 +14,7 @@ import BillDeleteButton from "../bill-delete-button/bill-delete-button.component
|
|||||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
//jobRO: selectJobReadOnly,
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -30,7 +29,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
export function BillsListTableComponent({
|
export function BillsListTableComponent({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
jobRO,
|
|
||||||
job,
|
job,
|
||||||
billsQuery,
|
billsQuery,
|
||||||
handleOnRowClick,
|
handleOnRowClick,
|
||||||
@@ -45,8 +43,6 @@ export function BillsListTableComponent({
|
|||||||
});
|
});
|
||||||
// const search = queryString.parse(useLocation().search);
|
// const search = queryString.parse(useLocation().search);
|
||||||
// const selectedBill = search.billid;
|
// const selectedBill = search.billid;
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
|
|
||||||
const Templates = TemplateList("bill");
|
const Templates = TemplateList("bill");
|
||||||
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
||||||
const { refetch } = billsQuery;
|
const { refetch } = billsQuery;
|
||||||
@@ -60,11 +56,10 @@ export function BillsListTableComponent({
|
|||||||
<BillDeleteButton bill={record} />
|
<BillDeleteButton bill={record} />
|
||||||
<Button
|
<Button
|
||||||
disabled={
|
disabled={
|
||||||
record.is_credit_memo ||
|
record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid
|
||||||
record.vendorid === bodyshop.inhousevendorid ||
|
|
||||||
jobRO
|
|
||||||
}
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
console.log(record);
|
||||||
setPartsOrderContext({
|
setPartsOrderContext({
|
||||||
actions: {},
|
actions: {},
|
||||||
context: {
|
context: {
|
||||||
@@ -172,24 +167,6 @@ export function BillsListTableComponent({
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredBills = bills
|
|
||||||
? searchText === ""
|
|
||||||
? bills
|
|
||||||
: bills.filter(
|
|
||||||
(b) =>
|
|
||||||
(b.invoice_number || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(b.vendor.name || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(b.total || "")
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase())
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("bills.labels.bills")}
|
title={t("bills.labels.bills")}
|
||||||
@@ -230,10 +207,8 @@ export function BillsListTableComponent({
|
|||||||
|
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
value={searchText}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSearchText(e.target.value);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -246,7 +221,7 @@ export function BillsListTableComponent({
|
|||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredBills}
|
dataSource={bills}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export default function CABCpvrtCalculator({ disabled, form }) {
|
|||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("job_ca_bc_pvrt_calculate");
|
logImEXEvent("job_ca_bc_pvrt_calculate");
|
||||||
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
|
form.setFieldsValue({ ca_bc_pvrt: (values.rate * values.days).toFixed(2) });
|
||||||
setVisibility(false);
|
setVisibility(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
@@ -20,7 +19,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
export default connect(mapStateToProps, mapDispatchToProps)(ChatMediaSelector);
|
||||||
|
|
||||||
export function ChatMediaSelector({
|
export function ChatMediaSelector({
|
||||||
bodyshop,
|
|
||||||
selectedMedia,
|
selectedMedia,
|
||||||
setSelectedMedia,
|
setSelectedMedia,
|
||||||
conversation,
|
conversation,
|
||||||
@@ -29,6 +27,7 @@ export function ChatMediaSelector({
|
|||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
const { loading, error, data } = useQuery(GET_DOCUMENTS_BY_JOB, {
|
||||||
|
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
variables: {
|
variables: {
|
||||||
@@ -67,8 +66,6 @@ export function ChatMediaSelector({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (bodyshop.uselocalmediaserver) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
content={
|
content={
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function ChatPresetsComponent({ bodyshop, setMessage, className }) {
|
|||||||
const menu = (
|
const menu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
{bodyshop.md_messaging_presets.map((i, idx) => (
|
{bodyshop.md_messaging_presets.map((i, idx) => (
|
||||||
<Menu.Item onClick={() => setMessage(i.text)} key={idx}>
|
<Menu.Item onClick={() => setMessage(i.text)} onItemHover key={idx}>
|
||||||
{i.label}
|
{i.label}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { SyncOutlined, WarningFilled } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Input, Space, Table, Tooltip } from "antd";
|
import { Button, Card, Input, Space, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
import moment from "moment";
|
|
||||||
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
@@ -56,25 +55,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
onFilter: (value, record) => value.includes(record.status),
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => t(record.status),
|
||||||
const { nextservicedate, nextservicekm, mileage } = record;
|
|
||||||
|
|
||||||
const mileageOver = nextservicekm <= mileage;
|
|
||||||
|
|
||||||
const dueForService =
|
|
||||||
nextservicedate && moment(nextservicedate).isBefore(moment());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space>
|
|
||||||
{t(record.status)}
|
|
||||||
{(mileageOver || dueForService) && (
|
|
||||||
<Tooltip title={t("contracts.labels.cardueforservice")}>
|
|
||||||
<WarningFilled style={{ color: "tomato" }} />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("courtesycars.fields.year"),
|
title: t("courtesycars.fields.year"),
|
||||||
@@ -124,17 +105,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
|||||||
</Link>
|
</Link>
|
||||||
) : null,
|
) : null,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("contracts.fields.scheduledreturn"),
|
|
||||||
dataIndex: "scheduledreturn",
|
|
||||||
key: "scheduledreturn",
|
|
||||||
render: (text, record) =>
|
|
||||||
record.cccontracts.length === 1 && (
|
|
||||||
<DateTimeFormatter>
|
|
||||||
{record.cccontracts[0].scheduledreturn}
|
|
||||||
</DateTimeFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import { UploadOutlined } from "@ant-design/icons";
|
|
||||||
import { Upload } from "antd";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import {
|
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import { handleUpload } from "./documents-local-upload.utility";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function DocumentsLocalUploadComponent({
|
|
||||||
children,
|
|
||||||
currentUser,
|
|
||||||
bodyshop,
|
|
||||||
job,
|
|
||||||
vendorid,
|
|
||||||
invoice_number,
|
|
||||||
callbackAfterUpload,
|
|
||||||
}) {
|
|
||||||
const [fileList, setFileList] = useState([]);
|
|
||||||
|
|
||||||
const handleDone = (uid) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
setFileList((fileList) => fileList.filter((x) => x.uid !== uid));
|
|
||||||
}, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Upload.Dragger
|
|
||||||
multiple={true}
|
|
||||||
fileList={fileList}
|
|
||||||
onChange={(f) => {
|
|
||||||
if (f.event && f.event.percent === 100) handleDone(f.file.uid);
|
|
||||||
|
|
||||||
setFileList(f.fileList);
|
|
||||||
}}
|
|
||||||
customRequest={(ev) =>
|
|
||||||
handleUpload({
|
|
||||||
ev,
|
|
||||||
context: {
|
|
||||||
jobid: job.id,
|
|
||||||
vendorid,
|
|
||||||
invoice_number,
|
|
||||||
callback: callbackAfterUpload,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx"
|
|
||||||
>
|
|
||||||
{children || (
|
|
||||||
<>
|
|
||||||
<p className="ant-upload-drag-icon">
|
|
||||||
<UploadOutlined />
|
|
||||||
</p>
|
|
||||||
<p className="ant-upload-text">
|
|
||||||
Click or drag files to this area to upload.
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Upload.Dragger>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default connect(mapStateToProps, null)(DocumentsLocalUploadComponent);
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import cleanAxios from "../../utils/CleanAxios";
|
|
||||||
import { store } from "../../redux/store";
|
|
||||||
import { addMediaForJob } from "../../redux/media/media.actions";
|
|
||||||
import normalizeUrl from "normalize-url";
|
|
||||||
|
|
||||||
export const handleUpload = async ({ ev, context }) => {
|
|
||||||
const { onError, onSuccess, onProgress, file } = ev;
|
|
||||||
const { jobid, invoice_number, vendorid, callbackAfterUpload } = context;
|
|
||||||
|
|
||||||
const bodyshop = store.getState().user.bodyshop;
|
|
||||||
var options = {
|
|
||||||
headers: {
|
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
ims_token: bodyshop.localmediatoken,
|
|
||||||
},
|
|
||||||
onUploadProgress: (e) => {
|
|
||||||
if (!!onProgress) onProgress({ percent: (e.loaded / e.total) * 100 });
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
formData.append("jobid", jobid);
|
|
||||||
if (invoice_number) {
|
|
||||||
formData.append("invoice_number", invoice_number);
|
|
||||||
formData.append("vendorid", vendorid);
|
|
||||||
}
|
|
||||||
formData.append("file", file);
|
|
||||||
|
|
||||||
const imexMediaServerResponse = await cleanAxios.post(
|
|
||||||
normalizeUrl(
|
|
||||||
`${bodyshop.localmediaserverhttp}/${
|
|
||||||
invoice_number ? "bills" : "jobs"
|
|
||||||
}/upload`
|
|
||||||
),
|
|
||||||
formData,
|
|
||||||
{
|
|
||||||
...options,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (imexMediaServerResponse.status !== 200) {
|
|
||||||
if (!!onError) {
|
|
||||||
onError(imexMediaServerResponse.statusText);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onSuccess && onSuccess(file);
|
|
||||||
store.dispatch(
|
|
||||||
addMediaForJob({
|
|
||||||
jobid,
|
|
||||||
media: imexMediaServerResponse.data.map((d) => {
|
|
||||||
return {
|
|
||||||
...d,
|
|
||||||
selected: false,
|
|
||||||
src: normalizeUrl(`${bodyshop.localmediaserverhttp}/${d.src}`),
|
|
||||||
thumbnail: normalizeUrl(
|
|
||||||
`${bodyshop.localmediaserverhttp}/${d.thumbnail}`
|
|
||||||
),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (callbackAfterUpload) {
|
|
||||||
callbackAfterUpload();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -54,7 +54,7 @@ export const uploadToCloudinary = async (
|
|||||||
//Set variables for getting the signed URL.
|
//Set variables for getting the signed URL.
|
||||||
let timestamp = Math.floor(Date.now() / 1000);
|
let timestamp = Math.floor(Date.now() / 1000);
|
||||||
let public_id = key;
|
let public_id = key;
|
||||||
let tags = `${bodyshop.imexshopid},${
|
let tags = `${bodyshop.textid},${
|
||||||
tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
|
tagsArray ? tagsArray.map((tag) => `${tag},`) : ""
|
||||||
}`;
|
}`;
|
||||||
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
|
||||||
|
|||||||
@@ -38,12 +38,6 @@ export function EmailDocumentsComponent({
|
|||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
|
||||||
selectedMedia &&
|
|
||||||
selectedMedia
|
|
||||||
.filter((s) => s.isSelected)
|
|
||||||
.reduce((acc, val) => (acc = acc + val.size), 0)
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{loading && <LoadingSpinner />}
|
{loading && <LoadingSpinner />}
|
||||||
@@ -51,12 +45,6 @@ export function EmailDocumentsComponent({
|
|||||||
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
|
||||||
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
|
||||||
) : null}
|
) : null}
|
||||||
{selectedMedia &&
|
|
||||||
selectedMedia
|
|
||||||
.filter((s) => s.isSelected)
|
|
||||||
.reduce((acc, val) => (acc = acc + val.size), 0) >= 9961472 ? (
|
|
||||||
<div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div>
|
|
||||||
) : null}
|
|
||||||
{data && (
|
{data && (
|
||||||
<JobDocumentsGalleryExternal
|
<JobDocumentsGalleryExternal
|
||||||
data={data ? data.documents : []}
|
data={data ? data.documents : []}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Menu,
|
Menu,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Button,
|
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -21,13 +20,10 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
|
||||||
import { selectEmailConfig } from "../../redux/email/email.selectors";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
emailConfig: selectEmailConfig,
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
@@ -38,7 +34,6 @@ export default connect(
|
|||||||
)(EmailOverlayComponent);
|
)(EmailOverlayComponent);
|
||||||
|
|
||||||
export function EmailOverlayComponent({
|
export function EmailOverlayComponent({
|
||||||
emailConfig,
|
|
||||||
form,
|
form,
|
||||||
selectedMediaState,
|
selectedMediaState,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
@@ -47,12 +42,7 @@ export function EmailOverlayComponent({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleClick = ({ item, key, keyPath }) => {
|
const handleClick = ({ item, key, keyPath }) => {
|
||||||
const email = item.props.value;
|
const email = item.props.value;
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({ to: _.uniq([...form.getFieldValue("to"), email]) });
|
||||||
to: _.uniq([
|
|
||||||
...form.getFieldValue("to"),
|
|
||||||
...(typeof email === "string" ? [email] : email),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
@@ -65,11 +55,6 @@ export function EmailOverlayComponent({
|
|||||||
{`${e.first_name} ${e.last_name}`}
|
{`${e.first_name} ${e.last_name}`}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
{bodyshop.md_to_emails.map((e, idx) => (
|
|
||||||
<Menu.Item value={e.emails} key={idx + "group"}>
|
|
||||||
{e.label}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -139,9 +124,7 @@ export function EmailOverlayComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Divider>{t("emails.labels.preview")}</Divider>
|
<Divider>{t("emails.labels.preview")}</Divider>
|
||||||
{bodyshop.attach_pdf_to_email && (
|
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
|
||||||
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -160,17 +143,10 @@ export function EmailOverlayComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
{!bodyshop.uselocalmediaserver && (
|
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
||||||
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
|
<EmailDocumentsComponent selectedMediaState={selectedMediaState} />
|
||||||
<EmailDocumentsComponent selectedMediaState={selectedMediaState} />
|
</Tabs.TabPane>
|
||||||
</Tabs.TabPane>
|
|
||||||
)}
|
|
||||||
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
|
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
|
||||||
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
|
|
||||||
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>
|
|
||||||
<Button>{t("documents.labels.openinexplorer")}</Button>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="fileList"
|
name="fileList"
|
||||||
valuePropName="fileList"
|
valuePropName="fileList"
|
||||||
@@ -180,23 +156,6 @@ export function EmailOverlayComponent({
|
|||||||
}
|
}
|
||||||
return e && e.fileList;
|
return e && e.fileList;
|
||||||
}}
|
}}
|
||||||
rules={[
|
|
||||||
({ getFieldValue }) => ({
|
|
||||||
validator(rule, value) {
|
|
||||||
const totalSize = value.reduce(
|
|
||||||
(acc, val) => (acc = acc + val.size),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
const limit = 9961472;
|
|
||||||
|
|
||||||
if (totalSize > limit) {
|
|
||||||
return Promise.reject(t("general.errors.sizelimit"));
|
|
||||||
}
|
|
||||||
return Promise.resolve();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Upload.Dragger
|
<Upload.Dragger
|
||||||
beforeUpload={Upload.LIST_IGNORE}
|
beforeUpload={Upload.LIST_IGNORE}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ class ErrorBoundary extends React.Component {
|
|||||||
|
|
||||||
static getDerivedStateFromError(error) {
|
static getDerivedStateFromError(error) {
|
||||||
console.log("ErrorBoundary -> getDerivedStateFromError -> error", error);
|
console.log("ErrorBoundary -> getDerivedStateFromError -> error", error);
|
||||||
|
|
||||||
return { hasErrored: true, error: error };
|
return { hasErrored: true, error: error };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { WarningOutlined } from "@ant-design/icons";
|
|
||||||
import { Space, Tooltip } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
const style = {
|
|
||||||
fontWeight: "bold",
|
|
||||||
color: "green",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ExportLogsCountDisplay({ logs }) {
|
|
||||||
const success = logs.filter((e) => e.successful).length;
|
|
||||||
const attempts = logs.length;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Space style={success > 0 ? style : {}}>
|
|
||||||
{`${success}/${attempts}`}
|
|
||||||
{success > 0 && (
|
|
||||||
<Tooltip title={t("exportlogs.labels.priorsuccesfulexport")}>
|
|
||||||
<WarningOutlined />
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import Icon, {
|
import Icon, {
|
||||||
BankFilled,
|
BankFilled,
|
||||||
BarChartOutlined,
|
BarChartOutlined,
|
||||||
CarFilled,
|
CarFilled,
|
||||||
ClockCircleFilled,
|
ClockCircleFilled,
|
||||||
CheckCircleOutlined,
|
|
||||||
DashboardFilled,
|
DashboardFilled,
|
||||||
DollarCircleFilled,
|
DollarCircleFilled,
|
||||||
ExportOutlined,
|
ExportOutlined,
|
||||||
@@ -84,12 +82,6 @@ function Header({
|
|||||||
setReportCenterContext,
|
setReportCenterContext,
|
||||||
recentItems,
|
recentItems,
|
||||||
}) {
|
}) {
|
||||||
const { Simple_Inventory } = useTreatments(
|
|
||||||
["Simple_Inventory"],
|
|
||||||
{},
|
|
||||||
bodyshop && bodyshop.imexshopid
|
|
||||||
);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -116,9 +108,6 @@ function Header({
|
|||||||
<Menu.Item key="activejobs" icon={<FileFilled />}>
|
<Menu.Item key="activejobs" icon={<FileFilled />}>
|
||||||
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
<Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="readyjobs" icon={<CheckCircleOutlined />}>
|
|
||||||
<Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="parts-queue" icon={<ToolFilled />}>
|
<Menu.Item key="parts-queue" icon={<ToolFilled />}>
|
||||||
<Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
<Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
@@ -206,20 +195,7 @@ function Header({
|
|||||||
>
|
>
|
||||||
{t("menus.header.enterbills")}
|
{t("menus.header.enterbills")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
{Simple_Inventory.treatment === "on" && (
|
<Menu.Divider key="div4" />
|
||||||
<>
|
|
||||||
<Menu.Divider key="div4" />
|
|
||||||
<Menu.Item
|
|
||||||
key="inventory"
|
|
||||||
icon={<Icon component={FaFileInvoiceDollar} />}
|
|
||||||
>
|
|
||||||
<Link to="/manage/inventory">
|
|
||||||
{t("menus.header.inventory")}
|
|
||||||
</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Menu.Divider key="div7" />
|
|
||||||
<Menu.Item key="allpayments" icon={<BankFilled />}>
|
<Menu.Item key="allpayments" icon={<BankFilled />}>
|
||||||
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
<Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
@@ -236,6 +212,7 @@ function Header({
|
|||||||
{t("menus.header.enterpayment")}
|
{t("menus.header.enterpayment")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Divider key="div5" />
|
<Menu.Divider key="div5" />
|
||||||
|
|
||||||
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
|
<Menu.Item key="timetickets" icon={<FieldTimeOutlined />}>
|
||||||
<Link to="/manage/timetickets">
|
<Link to="/manage/timetickets">
|
||||||
{t("menus.header.timetickets")}
|
{t("menus.header.timetickets")}
|
||||||
@@ -254,6 +231,7 @@ function Header({
|
|||||||
{t("menus.header.entertimeticket")}
|
{t("menus.header.entertimeticket")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Divider key="div6" />
|
<Menu.Divider key="div6" />
|
||||||
|
|
||||||
<Menu.SubMenu
|
<Menu.SubMenu
|
||||||
key="accountingexport"
|
key="accountingexport"
|
||||||
title={t("menus.header.export")}
|
title={t("menus.header.export")}
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
import { Button } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import moment from "moment";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
setBillEnterContext: (context) =>
|
|
||||||
dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(InventoryBillRo);
|
|
||||||
export function InventoryBillRo({
|
|
||||||
bodyshop,
|
|
||||||
setBillEnterContext,
|
|
||||||
inventoryline,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setBillEnterContext({
|
|
||||||
actions: {
|
|
||||||
//refetch: refetch
|
|
||||||
},
|
|
||||||
context: {
|
|
||||||
disableInvNumber: true,
|
|
||||||
//job: { id: job.id },
|
|
||||||
consumeinventoryid: inventoryline.id,
|
|
||||||
bill: {
|
|
||||||
vendorid: bodyshop.inhousevendorid,
|
|
||||||
invoice_number: "ih",
|
|
||||||
isinhouse: true,
|
|
||||||
date: moment(),
|
|
||||||
total: 0,
|
|
||||||
billlines: [{}],
|
|
||||||
// billlines: selectedLines.map((p) => {
|
|
||||||
// return {
|
|
||||||
// joblineid: p.id,
|
|
||||||
// actual_price: p.act_price,
|
|
||||||
// actual_cost: 0, //p.act_price,
|
|
||||||
// line_desc: p.line_desc,
|
|
||||||
// line_remarks: p.line_remarks,
|
|
||||||
// part_type: p.part_type,
|
|
||||||
// quantity: p.quantity || 1,
|
|
||||||
// applicable_taxes: {
|
|
||||||
// local: false,
|
|
||||||
// state: false,
|
|
||||||
// federal: false,
|
|
||||||
// },
|
|
||||||
// };
|
|
||||||
// }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("inventory.actions.addtoro")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Button, notification, Popconfirm } from "antd";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { DELETE_INVENTORY_LINE } from "../../graphql/inventory.queries";
|
|
||||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
|
||||||
|
|
||||||
export default function InventoryLineDelete({
|
|
||||||
inventoryline,
|
|
||||||
disabled,
|
|
||||||
refetch,
|
|
||||||
}) {
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [deleteInventoryLine] = useMutation(DELETE_INVENTORY_LINE);
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
const result = await deleteInventoryLine({
|
|
||||||
variables: { lineId: inventoryline.id },
|
|
||||||
// update(cache, { errors }) {
|
|
||||||
// cache.modify({
|
|
||||||
// fields: {
|
|
||||||
// inventory(existingInventory, { readField }) {
|
|
||||||
// console.log(existingInventory);
|
|
||||||
// return existingInventory.filter(
|
|
||||||
// (invRef) => inventoryline.id !== readField("id", invRef)
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!!!result.errors) {
|
|
||||||
notification["success"]({ message: t("inventory.successes.deleted") });
|
|
||||||
} else {
|
|
||||||
//Check if it's an fkey violation.
|
|
||||||
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bills.errors.deleting", {
|
|
||||||
error: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (refetch) refetch();
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RbacWrapper action="inventory:delete" noauth={<></>}>
|
|
||||||
<Popconfirm
|
|
||||||
disabled={disabled || inventoryline.consumedbybillid}
|
|
||||||
onConfirm={handleDelete}
|
|
||||||
title={t("inventory.labels.deleteconfirm")}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
disabled={disabled || inventoryline.consumedbybillid}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
<DeleteFilled />
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
</RbacWrapper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
import { EditFilled, SyncOutlined, FileAddFilled } from "@ant-design/icons";
|
|
||||||
import { Button, Card, Input, Space, Table, Typography } from "antd";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
||||||
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
|
|
||||||
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
setInventoryUpsertContext: (context) =>
|
|
||||||
dispatch(setModalContext({ context: context, modal: "inventoryUpsert" })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function JobsList({
|
|
||||||
bodyshop,
|
|
||||||
refetch,
|
|
||||||
loading,
|
|
||||||
jobs,
|
|
||||||
total,
|
|
||||||
setInventoryUpsertContext,
|
|
||||||
}) {
|
|
||||||
const search = queryString.parse(useLocation().search);
|
|
||||||
const { page, sortcolumn, sortorder } = search;
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
title: t("billlines.fields.line_desc"),
|
|
||||||
dataIndex: "line_desc",
|
|
||||||
key: "line_desc",
|
|
||||||
|
|
||||||
sorter: true, //(a, b) => alphaSort(a.line_desc, b.line_desc),
|
|
||||||
sortOrder: sortcolumn === "line_desc" && sortorder,
|
|
||||||
render: (text, record) =>
|
|
||||||
record.billline?.bill?.job ? (
|
|
||||||
<div>
|
|
||||||
<div>{text}</div>
|
|
||||||
<strong>{`(${record.billline?.bill?.job?.v_model_yr} ${record.billline?.bill?.job?.v_make_desc} ${record.billline?.bill?.job?.v_model_desc})`}</strong>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
text
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("inventory.labels.frombillinvoicenumber"),
|
|
||||||
dataIndex: "vendorname",
|
|
||||||
key: "vendorname",
|
|
||||||
ellipsis: true,
|
|
||||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
|
||||||
|
|
||||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
|
||||||
render: (text, record) =>
|
|
||||||
(
|
|
||||||
(record.billline?.bill?.invoice_number || "") +
|
|
||||||
" " +
|
|
||||||
(record.manualinvoicenumber || "")
|
|
||||||
).trim(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("inventory.labels.fromvendor"),
|
|
||||||
dataIndex: "vendorname",
|
|
||||||
key: "vendorname",
|
|
||||||
ellipsis: true,
|
|
||||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
|
||||||
|
|
||||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
|
||||||
render: (text, record) =>
|
|
||||||
(
|
|
||||||
(record.billline?.bill?.vendor?.name || "") +
|
|
||||||
" " +
|
|
||||||
(record.manualvendor || "")
|
|
||||||
).trim(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("billlines.fields.actual_price"),
|
|
||||||
dataIndex: "actual_price",
|
|
||||||
key: "actual_price",
|
|
||||||
|
|
||||||
render: (text, record) => (
|
|
||||||
<CurrencyFormatter>{record.actual_price}</CurrencyFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("billlines.fields.actual_cost"),
|
|
||||||
dataIndex: "actual_cost",
|
|
||||||
key: "actual_cost",
|
|
||||||
|
|
||||||
render: (text, record) => (
|
|
||||||
<CurrencyFormatter>{record.actual_cost}</CurrencyFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("inventory.fields.comment"),
|
|
||||||
dataIndex: "comment",
|
|
||||||
key: "comment",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("inventory.labels.consumedbyjob"),
|
|
||||||
dataIndex: "consumedbyjob",
|
|
||||||
key: "consumedbyjob",
|
|
||||||
|
|
||||||
ellipsis: true,
|
|
||||||
render: (text, record) =>
|
|
||||||
record.bill?.job?.ro_number ? (
|
|
||||||
<Link to={`/manage/jobs/${record.bill?.job?.id}`}>
|
|
||||||
{record.bill?.job?.ro_number}
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<InventoryBillRo inventoryline={record} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("general.labels.actions"),
|
|
||||||
dataIndex: "actions",
|
|
||||||
key: "actions",
|
|
||||||
|
|
||||||
ellipsis: true,
|
|
||||||
render: (text, record) => (
|
|
||||||
<Space wrap>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setInventoryUpsertContext({
|
|
||||||
actions: { refetch: refetch },
|
|
||||||
context: {
|
|
||||||
existingInventory: record,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EditFilled />
|
|
||||||
</Button>
|
|
||||||
<InventoryLineDelete inventoryline={record} refetch={refetch} />
|
|
||||||
</Space>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
|
||||||
search.page = pagination.current;
|
|
||||||
search.sortcolumn = sorter.column && sorter.column.key;
|
|
||||||
search.sortorder = sorter.order;
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
extra={
|
|
||||||
<Space wrap>
|
|
||||||
{search.search && (
|
|
||||||
<>
|
|
||||||
<Typography.Title level={4}>
|
|
||||||
{t("general.labels.searchresults", { search: search.search })}
|
|
||||||
</Typography.Title>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
delete search.search;
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t("general.actions.clear")}
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
setInventoryUpsertContext({
|
|
||||||
actions: { refetch: refetch },
|
|
||||||
context: {},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FileAddFilled />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (search.showall) delete search.showall;
|
|
||||||
else {
|
|
||||||
search.showall = true;
|
|
||||||
}
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{search.showall
|
|
||||||
? t("inventory.labels.showavailable")
|
|
||||||
: t("inventory.labels.showall")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button onClick={() => refetch()}>
|
|
||||||
<SyncOutlined />
|
|
||||||
</Button>
|
|
||||||
<Input.Search
|
|
||||||
placeholder={search.search || t("general.labels.search")}
|
|
||||||
onSearch={(value) => {
|
|
||||||
search.search = value;
|
|
||||||
history.push({ search: queryString.stringify(search) });
|
|
||||||
}}
|
|
||||||
enterButton
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table
|
|
||||||
loading={loading}
|
|
||||||
pagination={{
|
|
||||||
position: "top",
|
|
||||||
pageSize: 25,
|
|
||||||
current: parseInt(page || 1),
|
|
||||||
total: total,
|
|
||||||
}}
|
|
||||||
columns={columns}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={jobs}
|
|
||||||
onChange={handleTableChange}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsList);
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import React from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { useLocation } from "react-router-dom";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { QUERY_INVENTORY_PAGINATED } from "../../graphql/inventory.queries";
|
|
||||||
import {
|
|
||||||
setBreadcrumbs,
|
|
||||||
setSelectedHeader,
|
|
||||||
} from "../../redux/application/application.actions";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
|
||||||
import InventoryListPaginated from "./inventory-list.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
|
||||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
|
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
|
||||||
const { page, sortcolumn, sortorder, search, showall } = searchParams;
|
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(
|
|
||||||
QUERY_INVENTORY_PAGINATED,
|
|
||||||
{
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
variables: {
|
|
||||||
search: search || "",
|
|
||||||
offset: page ? (page - 1) * 25 : 0,
|
|
||||||
limit: 25,
|
|
||||||
consumedIsNull: showall === "true" ? null : true,
|
|
||||||
order: [
|
|
||||||
{
|
|
||||||
[sortcolumn || "created_at"]:
|
|
||||||
sortorder && sortorder !== "false"
|
|
||||||
? sortorder === "descend"
|
|
||||||
? "desc"
|
|
||||||
: "asc"
|
|
||||||
: "desc",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
|
||||||
return (
|
|
||||||
<InventoryListPaginated
|
|
||||||
refetch={refetch}
|
|
||||||
loading={loading}
|
|
||||||
searchParams={searchParams}
|
|
||||||
total={data ? data.search_inventory_aggregate.aggregate.count : 0}
|
|
||||||
jobs={data ? data.search_inventory : []}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(InventoryList);
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { Form, Input, Space } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectInventoryUpsert } from "../../redux/modals/modals.selectors";
|
|
||||||
import FormItemCurrency from "../form-items-formatted/currency-form-item.component";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
inventoryUpsertModal: selectInventoryUpsert,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(NoteUpsertModalComponent);
|
|
||||||
|
|
||||||
export function NoteUpsertModalComponent({ form, inventoryUpsertModal }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const { existingInventory } = inventoryUpsertModal.context;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space wrap>
|
|
||||||
<Form.Item
|
|
||||||
label={t("billlines.fields.line_desc")}
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
name="line_desc"
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label={t("inventory.fields.comment")} name="comment">
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{!existingInventory && (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
label={t("inventory.fields.manualinvoicenumber")}
|
|
||||||
name="manualinvoicenumber"
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("inventory.fields.manualvendor")}
|
|
||||||
name="manualvendor"
|
|
||||||
>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
label={t("billlines.fields.actual_cost")}
|
|
||||||
name="actual_cost"
|
|
||||||
>
|
|
||||||
<FormItemCurrency />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
rules={[{ required: true }]}
|
|
||||||
label={t("billlines.fields.actual_price")}
|
|
||||||
name="actual_price"
|
|
||||||
>
|
|
||||||
<FormItemCurrency />
|
|
||||||
</Form.Item>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Form, Modal, notification } from "antd";
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import {
|
|
||||||
INSERT_INVENTORY_LINE,
|
|
||||||
UPDATE_INVENTORY_LINE,
|
|
||||||
} from "../../graphql/inventory.queries";
|
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
|
||||||
import { selectInventoryUpsert } from "../../redux/modals/modals.selectors";
|
|
||||||
import {
|
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import InventoryUpsertModal from "./inventory-upsert-modal.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
inventoryUpsertModal: selectInventoryUpsert,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("inventoryUpsert")),
|
|
||||||
});
|
|
||||||
|
|
||||||
export function InventoryUpsertModalContainer({
|
|
||||||
currentUser,
|
|
||||||
bodyshop,
|
|
||||||
inventoryUpsertModal,
|
|
||||||
toggleModalVisible,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [insertInventory] = useMutation(INSERT_INVENTORY_LINE);
|
|
||||||
const [updateInventoryLine] = useMutation(UPDATE_INVENTORY_LINE);
|
|
||||||
|
|
||||||
const { visible, context, actions } = inventoryUpsertModal;
|
|
||||||
const { existingInventory } = context;
|
|
||||||
const { refetch } = actions;
|
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
//Required to prevent infinite looping.
|
|
||||||
if (existingInventory && visible) {
|
|
||||||
form.setFieldsValue(existingInventory);
|
|
||||||
} else if (!existingInventory && visible) {
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
}, [existingInventory, form, visible]);
|
|
||||||
|
|
||||||
const handleFinish = async (formValues) => {
|
|
||||||
const values = formValues;
|
|
||||||
|
|
||||||
if (existingInventory) {
|
|
||||||
logImEXEvent("inventory_update");
|
|
||||||
|
|
||||||
updateInventoryLine({
|
|
||||||
variables: {
|
|
||||||
inventoryId: existingInventory.id,
|
|
||||||
inventoryItem: values,
|
|
||||||
},
|
|
||||||
}).then((r) => {
|
|
||||||
notification["success"]({
|
|
||||||
message: t("inventory.successes.updated"),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// if (refetch) refetch();
|
|
||||||
toggleModalVisible();
|
|
||||||
} else {
|
|
||||||
logImEXEvent("inventory_insert");
|
|
||||||
|
|
||||||
await insertInventory({
|
|
||||||
variables: {
|
|
||||||
inventoryItem: { shopid: bodyshop.id, ...values },
|
|
||||||
},
|
|
||||||
update(cache, { data }) {
|
|
||||||
cache.modify({
|
|
||||||
fields: {
|
|
||||||
inventory(existingInv) {
|
|
||||||
return [...existingInv, data.insert_inventory_one];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (refetch) refetch();
|
|
||||||
form.resetFields();
|
|
||||||
toggleModalVisible();
|
|
||||||
notification["success"]({
|
|
||||||
message: t("inventory.successes.inserted"),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title={
|
|
||||||
existingInventory
|
|
||||||
? t("inventory.actions.edit")
|
|
||||||
: t("inventory.actions.new")
|
|
||||||
}
|
|
||||||
visible={visible}
|
|
||||||
okText={t("general.actions.save")}
|
|
||||||
onOk={() => {
|
|
||||||
form.submit();
|
|
||||||
}}
|
|
||||||
onCancel={() => {
|
|
||||||
toggleModalVisible();
|
|
||||||
}}
|
|
||||||
destroyOnClose
|
|
||||||
>
|
|
||||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
|
||||||
<InventoryUpsertModal form={form} />
|
|
||||||
</Form>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(InventoryUpsertModalContainer);
|
|
||||||
@@ -49,11 +49,11 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
|
|||||||
|
|
||||||
GenerateDocument(
|
GenerateDocument(
|
||||||
{
|
{
|
||||||
name: TemplateList("job_special").special_thirdpartypayer.key,
|
name: TemplateList("job_special").thirdpartypayer.key,
|
||||||
variables: { id: jobId },
|
variables: { id: jobId },
|
||||||
context: restVals,
|
context: restVals,
|
||||||
},
|
},
|
||||||
{ subject: TemplateList("job_special").special_thirdpartypayer.subject },
|
{ subject: TemplateList("job_special").thirdpartypayer.subject },
|
||||||
sendtype
|
sendtype
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ export default function JobBillsTotalComponent({
|
|||||||
let billCms = Dinero();
|
let billCms = Dinero();
|
||||||
let lbrAdjustments = Dinero();
|
let lbrAdjustments = Dinero();
|
||||||
let totalReturns = Dinero();
|
let totalReturns = Dinero();
|
||||||
let totalReturnsMarkedNotReceived = Dinero();
|
|
||||||
let totalReturnsMarkedReceived = Dinero();
|
|
||||||
|
|
||||||
partsOrders.forEach((p) =>
|
partsOrders.forEach((p) =>
|
||||||
p.parts_order_lines.forEach((pol) => {
|
p.parts_order_lines.forEach((pol) => {
|
||||||
@@ -37,24 +35,6 @@ export default function JobBillsTotalComponent({
|
|||||||
amount: Math.round((pol.act_price || 0) * 100),
|
amount: Math.round((pol.act_price || 0) * 100),
|
||||||
}).multiply(pol.quantity)
|
}).multiply(pol.quantity)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (pol.cm_received === null) {
|
|
||||||
return; // Skip this calculation for bills posted prior to the CNR change.
|
|
||||||
} else {
|
|
||||||
if (pol.cm_received === false) {
|
|
||||||
totalReturnsMarkedNotReceived = totalReturnsMarkedNotReceived.add(
|
|
||||||
Dinero({
|
|
||||||
amount: Math.round((pol.act_price || 0) * 100),
|
|
||||||
}).multiply(pol.quantity)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
totalReturnsMarkedReceived = totalReturnsMarkedReceived.add(
|
|
||||||
Dinero({
|
|
||||||
amount: Math.round((pol.act_price || 0) * 100),
|
|
||||||
}).multiply(pol.quantity)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -86,7 +66,6 @@ export default function JobBillsTotalComponent({
|
|||||||
|
|
||||||
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
const totalPartsSublet = Dinero(totals.parts.parts.total)
|
||||||
.add(Dinero(totals.parts.sublets.total))
|
.add(Dinero(totals.parts.sublets.total))
|
||||||
.add(Dinero(totals.additional.shipping))
|
|
||||||
.add(Dinero(totals.additional.towing));
|
.add(Dinero(totals.additional.towing));
|
||||||
|
|
||||||
const discrepancy = totalPartsSublet.subtract(billTotals);
|
const discrepancy = totalPartsSublet.subtract(billTotals);
|
||||||
@@ -94,7 +73,7 @@ export default function JobBillsTotalComponent({
|
|||||||
const discrepWithLbrAdj = discrepancy.add(lbrAdjustments);
|
const discrepWithLbrAdj = discrepancy.add(lbrAdjustments);
|
||||||
|
|
||||||
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
|
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
|
||||||
const calculatedCreditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
|
const creditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={16}>
|
<Row gutter={16}>
|
||||||
@@ -234,32 +213,6 @@ export default function JobBillsTotalComponent({
|
|||||||
value={totalReturns.toFormat()}
|
value={totalReturns.toFormat()}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip
|
|
||||||
title={
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{
|
|
||||||
__html: t(
|
|
||||||
"jobs.labels.plitooltips.calculatedcreditsnotreceived"
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Statistic
|
|
||||||
title={t("bills.labels.calculatedcreditsnotreceived")}
|
|
||||||
valueStyle={{
|
|
||||||
color:
|
|
||||||
calculatedCreditsNotReceived.getAmount() <= 0
|
|
||||||
? "green"
|
|
||||||
: "red",
|
|
||||||
}}
|
|
||||||
value={
|
|
||||||
calculatedCreditsNotReceived.getAmount() >= 0
|
|
||||||
? calculatedCreditsNotReceived.toFormat()
|
|
||||||
: Dinero().toFormat()
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
title={
|
title={
|
||||||
<div
|
<div
|
||||||
@@ -272,14 +225,11 @@ export default function JobBillsTotalComponent({
|
|||||||
<Statistic
|
<Statistic
|
||||||
title={t("bills.labels.creditsnotreceived")}
|
title={t("bills.labels.creditsnotreceived")}
|
||||||
valueStyle={{
|
valueStyle={{
|
||||||
color:
|
color: creditsNotReceived.getAmount() <= 0 ? "green" : "red",
|
||||||
totalReturnsMarkedNotReceived.getAmount() <= 0
|
|
||||||
? "green"
|
|
||||||
: "red",
|
|
||||||
}}
|
}}
|
||||||
value={
|
value={
|
||||||
totalReturnsMarkedNotReceived.getAmount() >= 0
|
creditsNotReceived.getAmount() >= 0
|
||||||
? totalReturnsMarkedNotReceived.toFormat()
|
? creditsNotReceived.toFormat()
|
||||||
: Dinero().toFormat()
|
: Dinero().toFormat()
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ import React from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
import { Link, useHistory, useLocation } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
|
import { QUERY_JOB_CARD_DETAILS } from "../../graphql/jobs.queries";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobSyncButton from "../job-sync-button/job-sync-button.component";
|
import JobSyncButton from "../job-sync-button/job-sync-button.component";
|
||||||
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component";
|
import JobsDetailHeader from "../jobs-detail-header/jobs-detail-header.component";
|
||||||
@@ -22,10 +20,6 @@ import JobDetailCardsNotesComponent from "./job-detail-cards.notes.component";
|
|||||||
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
import JobDetailCardsPartsComponent from "./job-detail-cards.parts.component";
|
||||||
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
import JobDetailCardsTotalsComponent from "./job-detail-cards.totals.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPrintCenterContext: (context) =>
|
setPrintCenterContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||||
@@ -37,7 +31,7 @@ const span = {
|
|||||||
lg: { span: 8 },
|
lg: { span: 8 },
|
||||||
};
|
};
|
||||||
|
|
||||||
export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
|
export function JobDetailCards({ setPrintCenterContext }) {
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
.filter((screen) => !!screen[1])
|
.filter((screen) => !!screen[1])
|
||||||
.slice(-1)[0];
|
.slice(-1)[0];
|
||||||
@@ -149,14 +143,12 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
|
|||||||
data={data ? data.jobs_by_pk : null}
|
data={data ? data.jobs_by_pk : null}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
{!bodyshop.uselocalmediaserver && (
|
<Col {...span}>
|
||||||
<Col {...span}>
|
<JobDetailCardsDocumentsComponent
|
||||||
<JobDetailCardsDocumentsComponent
|
loading={loading}
|
||||||
loading={loading}
|
data={data ? data.jobs_by_pk : null}
|
||||||
data={data ? data.jobs_by_pk : null}
|
/>
|
||||||
/>
|
</Col>
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
<Col {...span}>
|
<Col {...span}>
|
||||||
<JobDetailCardsDamageComponent
|
<JobDetailCardsDamageComponent
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -169,4 +161,4 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext }) {
|
|||||||
</Drawer>
|
</Drawer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobDetailCards);
|
export default connect(null, mapDispatchToProps)(JobDetailCards);
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
EditFilled,
|
EditFilled,
|
||||||
PlusCircleTwoTone,
|
PlusCircleTwoTone,
|
||||||
MinusCircleTwoTone,
|
MinusCircleTwoTone,
|
||||||
HomeOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import {
|
import {
|
||||||
@@ -43,7 +42,6 @@ import _ from "lodash";
|
|||||||
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
|
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
|
||||||
import JobLinesExpander from "./job-lines-expander.component";
|
import JobLinesExpander from "./job-lines-expander.component";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import moment from "moment";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -56,8 +54,6 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
|
dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
|
||||||
setPartsOrderContext: (context) =>
|
setPartsOrderContext: (context) =>
|
||||||
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||||
setBillEnterContext: (context) =>
|
|
||||||
dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export function JobLinesComponent({
|
export function JobLinesComponent({
|
||||||
@@ -72,7 +68,6 @@ export function JobLinesComponent({
|
|||||||
job,
|
job,
|
||||||
setJobLineEditContext,
|
setJobLineEditContext,
|
||||||
form,
|
form,
|
||||||
setBillEnterContext,
|
|
||||||
}) {
|
}) {
|
||||||
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
||||||
|
|
||||||
@@ -391,62 +386,6 @@ export function JobLinesComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
<Button
|
|
||||||
disabled={
|
|
||||||
(job && !job.converted) ||
|
|
||||||
(selectedLines.length > 0 ? false : true) ||
|
|
||||||
jobRO ||
|
|
||||||
technician
|
|
||||||
}
|
|
||||||
onClick={() => {
|
|
||||||
// setPartsOrderContext({
|
|
||||||
// actions: { refetch: refetch },
|
|
||||||
// context: {
|
|
||||||
// jobId: job.id,
|
|
||||||
// job: job,
|
|
||||||
// linesToOrder: selectedLines,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
setBillEnterContext({
|
|
||||||
actions: { refetch: refetch },
|
|
||||||
context: {
|
|
||||||
disableInvNumber: true,
|
|
||||||
job: { id: job.id },
|
|
||||||
bill: {
|
|
||||||
vendorid: bodyshop.inhousevendorid,
|
|
||||||
invoice_number: "ih",
|
|
||||||
isinhouse: true,
|
|
||||||
date: new moment(),
|
|
||||||
total: 0,
|
|
||||||
billlines: selectedLines.map((p) => {
|
|
||||||
return {
|
|
||||||
joblineid: p.id,
|
|
||||||
actual_price: p.act_price,
|
|
||||||
actual_cost: 0, //p.act_price,
|
|
||||||
line_desc: p.line_desc,
|
|
||||||
line_remarks: p.line_remarks,
|
|
||||||
part_type: p.part_type,
|
|
||||||
quantity: p.quantity || 1,
|
|
||||||
applicable_taxes: {
|
|
||||||
local: false,
|
|
||||||
state: false,
|
|
||||||
federal: false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
//Clear out the selected lines. IO-785
|
|
||||||
setSelectedLines([]);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<HomeOutlined />
|
|
||||||
{t("parts.actions.orderinhouse")}
|
|
||||||
{selectedLines.length > 0 && ` (${selectedLines.length})`}
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
disabled={
|
disabled={
|
||||||
(job && !job.converted) ||
|
(job && !job.converted) ||
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function JoblinePresetButton({ bodyshop, form }) {
|
|||||||
const menu = (
|
const menu = (
|
||||||
<Menu>
|
<Menu>
|
||||||
{bodyshop.md_jobline_presets.map((i, idx) => (
|
{bodyshop.md_jobline_presets.map((i, idx) => (
|
||||||
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
|
<Menu.Item onClick={() => handleSelect(i)} onItemHover key={idx}>
|
||||||
{i.label}
|
{i.label}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -141,9 +141,7 @@ export function JobLinesUpsertModalComponent({
|
|||||||
rules={[
|
rules={[
|
||||||
({ getFieldValue }) => ({
|
({ getFieldValue }) => ({
|
||||||
validator(rule, value) {
|
validator(rule, value) {
|
||||||
if (
|
if (!!getFieldValue("mod_lbr_ty") === !!value) {
|
||||||
!!getFieldValue("mod_lbr_ty") === (!!value || value === 0)
|
|
||||||
) {
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
import React, { useMemo } from "react";
|
|
||||||
import { Row, Col, Tag, Tooltip } from "antd";
|
|
||||||
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)(JobPartsQueueCount);
|
|
||||||
|
|
||||||
export function JobPartsQueueCount({ bodyshop, parts, style }) {
|
|
||||||
const partsStatus = useMemo(() => {
|
|
||||||
if (!parts) return null;
|
|
||||||
return parts.reduce(
|
|
||||||
(acc, val) => {
|
|
||||||
if (val.part_type === "PAS" || val.part_type === "PASL") return acc;
|
|
||||||
acc.total = acc.total + val.count;
|
|
||||||
acc[val.status] = acc[val.status] + val.count;
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
total: 0,
|
|
||||||
null: 0,
|
|
||||||
[bodyshop.md_order_statuses.default_bo]: 0,
|
|
||||||
[bodyshop.md_order_statuses.default_ordered]: 0,
|
|
||||||
[bodyshop.md_order_statuses.default_received]: 0,
|
|
||||||
[bodyshop.md_order_statuses.default_returned]: 0,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, [bodyshop, parts]);
|
|
||||||
|
|
||||||
if (!parts) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row style={style}>
|
|
||||||
<Col span={4}>
|
|
||||||
<Tooltip title="Total">
|
|
||||||
<Tag>{partsStatus.total}</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<Tooltip title="No Status">
|
|
||||||
<Tag color="gold">{partsStatus["null"]}</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_ordered}>
|
|
||||||
<Tag color="blue">
|
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_ordered]}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_received}>
|
|
||||||
<Tag color="green">
|
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_received]}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_returned}>
|
|
||||||
<Tag color="orange">
|
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_returned]}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
<Col span={4}>
|
|
||||||
<Tooltip title={bodyshop.md_order_statuses.default_bo}>
|
|
||||||
<Tag color="red">
|
|
||||||
{partsStatus[bodyshop.md_order_statuses.default_bo]}
|
|
||||||
</Tag>
|
|
||||||
</Tooltip>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -22,8 +22,7 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
|||||||
(j.part_type !== null && j.part_type !== "PAE") ||
|
(j.part_type !== null && j.part_type !== "PAE") ||
|
||||||
(j.line_desc &&
|
(j.line_desc &&
|
||||||
j.line_desc.toLowerCase().includes("towing") &&
|
j.line_desc.toLowerCase().includes("towing") &&
|
||||||
j.lbr_op === "OP13") ||
|
j.lbr_op === "OP13")
|
||||||
j.db_ref === "936004" //ADD SHIPPING LINE.
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
export const reconcileByAssocLine = (
|
export const reconcileByAssocLine = (
|
||||||
jobLines,
|
jobLines,
|
||||||
jobLineState,
|
jobLineState,
|
||||||
@@ -74,12 +73,7 @@ export const reconcileByPrice = (
|
|||||||
|
|
||||||
jobLines.forEach((jl) => {
|
jobLines.forEach((jl) => {
|
||||||
const matchingBillLineIds = billLines
|
const matchingBillLineIds = billLines
|
||||||
.filter(
|
.filter((bl) => bl.actual_price === jl.act_price && bl.quantity === jl.part_qty && !jl.removed)
|
||||||
(bl) =>
|
|
||||||
bl.actual_price === jl.act_price &&
|
|
||||||
bl.quantity === jl.part_qty &&
|
|
||||||
!jl.removed
|
|
||||||
)
|
|
||||||
.map((bl) => bl.id);
|
.map((bl) => bl.id);
|
||||||
|
|
||||||
if (matchingBillLineIds.length > 1) {
|
if (matchingBillLineIds.length > 1) {
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { Button, notification } from "antd";
|
||||||
import { Checkbox, notification, Space, Spin } from "antd";
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useMutation } from "@apollo/client";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function JobRemoveFromPartsQueue({ checked, jobId }) {
|
export default function JobRemoveFromPartsQueue({ jobId, refetch }) {
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
const handleClick = async (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const result = await updateJob({
|
const result = await updateJob({
|
||||||
variables: { jobId: jobId, job: { queued_for_parts: e.target.checked } },
|
variables: { jobId: jobId, job: { queued_for_parts: false } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!!!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({ message: t("jobs.successes.save") });
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
|
if (refetch) refetch();
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.saving", {
|
message: t("jobs.errors.saving", {
|
||||||
@@ -29,9 +30,8 @@ export default function JobRemoveFromPartsQueue({ checked, jobId }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space>
|
<Button onClick={handleClick} loading={loading}>
|
||||||
<Checkbox checked={checked} onChange={handleChange} />
|
{t("general.actions.remove")}
|
||||||
{loading && <Spin size="small" />}
|
</Button>
|
||||||
</Space>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ export default function ScoreboardAddButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover content={overlay} visible={visibility} placement="bottom">
|
<Popover content={overlay} visible={visibility}>
|
||||||
<Button
|
<Button
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
import { LoadingOutlined } from "@ant-design/icons";
|
||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
import { Select, Space, Tag } from "antd";
|
import { Empty, Select, Space, Tag } from "antd";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { forwardRef, useState, useEffect } from "react";
|
import React, { forwardRef, useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE,
|
SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE,
|
||||||
@@ -23,35 +24,31 @@ const JobSearchSelect = (
|
|||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [theOptions, setTheOptions] = useState([]);
|
|
||||||
const [callSearch, { loading, error, data }] = useLazyQuery(
|
const [callSearch, { loading, error, data }] = useLazyQuery(
|
||||||
SEARCH_JOBS_FOR_AUTOCOMPLETE,
|
SEARCH_JOBS_FOR_AUTOCOMPLETE,
|
||||||
{}
|
{
|
||||||
|
...(convertedOnly || notExported
|
||||||
|
? {
|
||||||
|
variables: {
|
||||||
|
...(convertedOnly ? { isConverted: true } : {}),
|
||||||
|
...(notExported ? { notExported: true } : {}),
|
||||||
|
...(notInvoiced ? { notInvoiced: true } : {}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
|
const [callIdSearch, { loading: idLoading, error: idError, data: idData }] =
|
||||||
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
useLazyQuery(SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE);
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (v) => {
|
||||||
if (v && v !== "") callSearch(v);
|
callSearch(v);
|
||||||
};
|
};
|
||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 500);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
debouncedExecuteSearch({
|
debouncedExecuteSearch({ variables: { search: value } });
|
||||||
variables: {
|
|
||||||
search: value,
|
|
||||||
...(convertedOnly || notExported
|
|
||||||
? {
|
|
||||||
variables: {
|
|
||||||
...(convertedOnly ? { isConverted: true } : {}),
|
|
||||||
...(notExported ? { notExported: true } : {}),
|
|
||||||
...(notInvoiced ? { notInvoiced: true } : {}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -60,17 +57,13 @@ const JobSearchSelect = (
|
|||||||
}
|
}
|
||||||
}, [restProps.value, callIdSearch]);
|
}, [restProps.value, callIdSearch]);
|
||||||
|
|
||||||
useEffect(() => {
|
const theOptions = _.uniqBy(
|
||||||
setTheOptions(
|
[
|
||||||
_.uniqBy(
|
...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []),
|
||||||
[
|
...(data && data.search_jobs ? data.search_jobs : []),
|
||||||
...(idData && idData.jobs_by_pk ? [idData.jobs_by_pk] : []),
|
],
|
||||||
...(data && data.search_jobs ? data.search_jobs : []),
|
"id"
|
||||||
],
|
);
|
||||||
"id"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}, [data, idData]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -84,8 +77,7 @@ const JobSearchSelect = (
|
|||||||
}}
|
}}
|
||||||
filterOption={false}
|
filterOption={false}
|
||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
loading={loading || idLoading}
|
notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
||||||
//notFoundContent={loading ? <LoadingOutlined /> : <Empty />}
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
{theOptions
|
{theOptions
|
||||||
@@ -107,6 +99,7 @@ const JobSearchSelect = (
|
|||||||
))
|
))
|
||||||
: null}
|
: null}
|
||||||
</Select>
|
</Select>
|
||||||
|
{idLoading || loading ? <LoadingOutlined /> : null}
|
||||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||||
{idError ? (
|
{idError ? (
|
||||||
<AlertComponent message={idError.message} type="error" />
|
<AlertComponent message={idError.message} type="error" />
|
||||||
|
|||||||
@@ -6,20 +6,17 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
|
||||||
|
|
||||||
export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
export function JobsAdminStatus({ bodyshop, job }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
|
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
|
||||||
@@ -29,10 +26,6 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
|
|||||||
})
|
})
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
notification["success"]({ message: t("jobs.successes.save") });
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.admin_jobstatuschange(status),
|
|
||||||
});
|
|
||||||
// refetch();
|
// refetch();
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -7,27 +7,8 @@ import DateTimePicker from "../form-date-time-picker/form-date-time-picker.compo
|
|||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
export default function JobsAdminDatesChange({ job }) {
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(JobsAdminDatesChange);
|
|
||||||
|
|
||||||
export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -39,23 +20,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
variables: { jobId: job.id, job: values },
|
variables: { jobId: job.id, job: values },
|
||||||
});
|
});
|
||||||
|
|
||||||
const changedAuditFields = form.getFieldsValue(
|
|
||||||
true,
|
|
||||||
(meta) => meta && meta.touched
|
|
||||||
);
|
|
||||||
|
|
||||||
Object.keys(changedAuditFields).forEach((key) => {
|
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.admin_jobfieldchange(
|
|
||||||
key,
|
|
||||||
changedAuditFields[key] instanceof moment
|
|
||||||
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
|
|
||||||
: changedAuditFields[key]
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!!!result.errors) {
|
if (!!!result.errors) {
|
||||||
notification["success"]({ message: t("jobs.successes.save") });
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -6,36 +6,22 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import {
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
|
||||||
});
|
});
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
mapStateToProps,
|
||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(JobAdminMarkReexport);
|
)(JobAdminMarkReexport);
|
||||||
|
|
||||||
export function JobAdminMarkReexport({
|
export function JobAdminMarkReexport({ bodyshop, job }) {
|
||||||
insertAuditTrail,
|
|
||||||
bodyshop,
|
|
||||||
currentUser,
|
|
||||||
job,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
|
||||||
const [markJobForReexport] = useMutation(gql`
|
const [markJobForReexport] = useMutation(gql`
|
||||||
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
|
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
|
||||||
update_jobs_by_pk(
|
update_jobs_by_pk(
|
||||||
@@ -92,10 +78,6 @@ export function JobAdminMarkReexport({
|
|||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({ message: t("jobs.successes.save") });
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.admin_jobmarkforreexport(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.saving", {
|
message: t("jobs.errors.saving", {
|
||||||
@@ -112,26 +94,8 @@ export function JobAdminMarkReexport({
|
|||||||
variables: { jobId: job.id, date_exported: moment() },
|
variables: { jobId: job.id, date_exported: moment() },
|
||||||
});
|
});
|
||||||
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
jobid: job.id,
|
|
||||||
successful: true,
|
|
||||||
message: JSON.stringify([t("general.labels.markedexported")]),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({ message: t("jobs.successes.save") });
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.admin_jobmarkexported(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.saving", {
|
message: t("jobs.errors.saving", {
|
||||||
|
|||||||
@@ -4,29 +4,21 @@ import React, { useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation }) =>
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
dispatch(insertAuditTrail({ jobid, operation })),
|
|
||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid);
|
||||||
|
|
||||||
export function JobsAdminUnvoid({
|
export function JobsAdminUnvoid({ bodyshop, job, currentUser }) {
|
||||||
insertAuditTrail,
|
|
||||||
bodyshop,
|
|
||||||
job,
|
|
||||||
currentUser,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [updateJob] = useMutation(gql`
|
const [updateJob] = useMutation(gql`
|
||||||
@@ -92,11 +84,6 @@ mutation UNVOID_JOB($jobId: uuid!) {
|
|||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({ message: t("jobs.successes.save") });
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
|
|
||||||
insertAuditTrail({
|
|
||||||
jobid: job.id,
|
|
||||||
operation: AuditTrailMapping.admin_unvoicejob(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification["error"]({
|
||||||
message: t("jobs.errors.saving", {
|
message: t("jobs.errors.saving", {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -26,7 +27,6 @@ export function JobsCloseExportButton({
|
|||||||
jobId,
|
jobId,
|
||||||
disabled,
|
disabled,
|
||||||
setSelectedJobs,
|
setSelectedJobs,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -46,10 +46,13 @@ export function JobsCloseExportButton({
|
|||||||
//Check if it's a QBO Setup.
|
//Check if it's a QBO Setup.
|
||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
PartnerResponse = await axios.post(
|
||||||
jobIds: [jobId],
|
`/qbo/receivables`,
|
||||||
elgen: true,
|
{
|
||||||
});
|
jobIds: [jobId],
|
||||||
|
},
|
||||||
|
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
//Default is QBD
|
//Default is QBD
|
||||||
|
|
||||||
@@ -114,64 +117,58 @@ export function JobsCloseExportButton({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
await insertExportLog({
|
||||||
//QBO Logs are handled server side.
|
variables: {
|
||||||
await insertExportLog({
|
logs: [
|
||||||
variables: {
|
{
|
||||||
logs: [
|
bodyshopid: bodyshop.id,
|
||||||
{
|
jobid: jobId,
|
||||||
bodyshopid: bodyshop.id,
|
successful: false,
|
||||||
jobid: jobId,
|
message: JSON.stringify(
|
||||||
successful: false,
|
failedTransactions.map((ft) => ft.errorMessage)
|
||||||
message: JSON.stringify(
|
),
|
||||||
failedTransactions.map((ft) => ft.errorMessage)
|
useremail: currentUser.email,
|
||||||
),
|
},
|
||||||
useremail: currentUser.email,
|
],
|
||||||
},
|
},
|
||||||
],
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
//Insert success export log.
|
//Insert success export log.
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
await insertExportLog({
|
||||||
//QBO Logs are handled server side.
|
variables: {
|
||||||
await insertExportLog({
|
logs: [
|
||||||
variables: {
|
{
|
||||||
logs: [
|
bodyshopid: bodyshop.id,
|
||||||
{
|
jobid: jobId,
|
||||||
bodyshopid: bodyshop.id,
|
successful: true,
|
||||||
jobid: jobId,
|
useremail: currentUser.email,
|
||||||
successful: true,
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const jobUpdateResponse = await updateJob({
|
|
||||||
variables: {
|
|
||||||
jobId: jobId,
|
|
||||||
job: {
|
|
||||||
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
|
|
||||||
date_exported: new Date(),
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!jobUpdateResponse.errors) {
|
const jobUpdateResponse = await updateJob({
|
||||||
notification.open({
|
variables: {
|
||||||
type: "success",
|
jobId: jobId,
|
||||||
key: "jobsuccessexport",
|
job: {
|
||||||
message: t("jobs.successes.exported"),
|
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||||
});
|
date_exported: new Date(),
|
||||||
} else {
|
},
|
||||||
notification["error"]({
|
},
|
||||||
message: t("jobs.errors.exporting", {
|
});
|
||||||
error: JSON.stringify(jobUpdateResponse.error),
|
|
||||||
}),
|
if (!jobUpdateResponse.errors) {
|
||||||
});
|
notification.open({
|
||||||
}
|
type: "success",
|
||||||
|
key: "jobsuccessexport",
|
||||||
|
message: t("jobs.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.exporting", {
|
||||||
|
error: JSON.stringify(jobUpdateResponse.error),
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (setSelectedJobs) {
|
if (setSelectedJobs) {
|
||||||
setSelectedJobs((selectedJobs) => {
|
setSelectedJobs((selectedJobs) => {
|
||||||
@@ -179,7 +176,7 @@ export function JobsCloseExportButton({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -146,11 +146,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
<Collapse.Panel
|
<Collapse.Panel forceRender key="claim" header={t("menus.jobsdetail.claimdetail")}>
|
||||||
forceRender
|
|
||||||
key="claim"
|
|
||||||
header={t("menus.jobsdetail.claimdetail")}
|
|
||||||
>
|
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
|
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -197,8 +193,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
<Collapse.Panel
|
<Collapse.Panel forceRender
|
||||||
forceRender
|
|
||||||
key="financial"
|
key="financial"
|
||||||
header={t("menus.jobsdetail.financials")}
|
header={t("menus.jobsdetail.financials")}
|
||||||
>
|
>
|
||||||
@@ -209,7 +204,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<CurrencyInput min={0} />
|
<CurrencyInput min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||||
<Select allowClear>
|
<Select>
|
||||||
<Select.Option value="W">
|
<Select.Option value="W">
|
||||||
{t("jobs.labels.deductible.waived")}
|
{t("jobs.labels.deductible.waived")}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
|
|||||||
@@ -16,18 +16,15 @@ export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu
|
<div>
|
||||||
onClick={handleClick}
|
<Menu onClick={handleClick}>
|
||||||
style={{
|
{bodyshop.md_filehandlers.map((est, idx) => (
|
||||||
columnCount: Math.floor(bodyshop.md_filehandlers.length / 10) + 1,
|
<Menu.Item value={est} key={idx}>
|
||||||
}}
|
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
|
||||||
>
|
</Menu.Item>
|
||||||
{bodyshop.md_filehandlers.map((est, idx) => (
|
))}
|
||||||
<Menu.Item value={est} key={idx} style={{ breakInside: "avoid" }}>
|
</Menu>
|
||||||
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
|
</div>
|
||||||
</Menu.Item>
|
|
||||||
))}
|
|
||||||
</Menu>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -216,22 +216,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
<DataLabel label={t("jobs.labels.relatedros")}>
|
<DataLabel label={t("jobs.labels.relatedros")}>
|
||||||
<JobsRelatedRos jobid={job.id} job={job} />
|
<JobsRelatedRos jobid={job.id} job={job} />
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
{job.vehicle && job.vehicle.notes && (
|
|
||||||
<DataLabel label={t("vehicles.fields.notes")}>
|
|
||||||
<span style={{ whiteSpace: "pre" }}>{job.vehicle.notes}</span>
|
|
||||||
</DataLabel>
|
|
||||||
)}
|
|
||||||
{
|
|
||||||
// job.vehicle && job.vehicle.v_paint_codes && (
|
|
||||||
// <DataLabel label={t("vehicles.fields.v_paint_codes")}>
|
|
||||||
// <span style={{ whiteSpace: "pre" }}>
|
|
||||||
// {Object.keys(job.vehicle.v_paint_codes).map((key, idx) => (
|
|
||||||
// <Tag key={idx}>{job.vehicle.v_paint_codes[key]}</Tag>
|
|
||||||
// ))}
|
|
||||||
// </span>
|
|
||||||
// </DataLabel>
|
|
||||||
// )
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
noStyle
|
nostyle
|
||||||
shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}
|
shouldUpdate={(prev, cur) => prev.auto_add_ats !== cur.auto_add_ats}
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
|
import { FileExcelFilled, EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Card, Col, Row, Space } from "antd";
|
import { Card, Col, Row, Space, Button } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Gallery from "react-grid-gallery";
|
import Gallery from "react-grid-gallery";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ function JobsDocumentGalleryExternal({
|
|||||||
id: value.id,
|
id: value.id,
|
||||||
type: value.type,
|
type: value.type,
|
||||||
tags: [{ value: value.type, title: value.type }],
|
tags: [{ value: value.type, title: value.type }],
|
||||||
size: value.size,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
|
||||||
import { Button, Card, Space } from "antd";
|
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import Gallery from "react-grid-gallery";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import {
|
|
||||||
getBillMedia,
|
|
||||||
getJobMedia,
|
|
||||||
toggleMediaSelected,
|
|
||||||
} from "../../redux/media/media.actions";
|
|
||||||
import { selectAllMedia } from "../../redux/media/media.selectors";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
|
|
||||||
import DocumentsLocalUploadComponent from "../documents-local-upload/documents-local-upload.component";
|
|
||||||
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
|
|
||||||
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
allMedia: selectAllMedia,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
getJobMedia: (id) => dispatch(getJobMedia(id)),
|
|
||||||
getBillMedia: ({ jobid, invoice_number }) => {
|
|
||||||
dispatch(getBillMedia({ jobid, invoice_number }));
|
|
||||||
},
|
|
||||||
toggleMediaSelected: ({ jobid, filename }) =>
|
|
||||||
dispatch(toggleMediaSelected({ jobid, filename })),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(JobsDocumentsLocalGallery);
|
|
||||||
|
|
||||||
export function JobsDocumentsLocalGallery({
|
|
||||||
bodyshop,
|
|
||||||
toggleMediaSelected,
|
|
||||||
getJobMedia,
|
|
||||||
getBillMedia,
|
|
||||||
allMedia,
|
|
||||||
job,
|
|
||||||
invoice_number,
|
|
||||||
vendorid,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
useEffect(() => {
|
|
||||||
if (job) {
|
|
||||||
if (invoice_number) {
|
|
||||||
getBillMedia({ jobid: job.id, invoice_number });
|
|
||||||
} else {
|
|
||||||
getJobMedia(job.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [job, invoice_number, getJobMedia, getBillMedia]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Space wrap>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (job) {
|
|
||||||
if (invoice_number) {
|
|
||||||
getBillMedia({ jobid: job.id, invoice_number });
|
|
||||||
} else {
|
|
||||||
getJobMedia(job.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SyncOutlined />
|
|
||||||
</Button>
|
|
||||||
<a href={CreateExplorerLinkForJob({ jobid: job.id })}>
|
|
||||||
<Button>{t("documents.labels.openinexplorer")}</Button>
|
|
||||||
</a>
|
|
||||||
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
|
|
||||||
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
|
|
||||||
</Space>
|
|
||||||
<Card>
|
|
||||||
<DocumentsLocalUploadComponent
|
|
||||||
job={job}
|
|
||||||
invoice_number={invoice_number}
|
|
||||||
vendorid={vendorid}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
<Card title={t("jobs.labels.documents-images")}>
|
|
||||||
<Gallery
|
|
||||||
images={(allMedia && allMedia[job.id]) || []}
|
|
||||||
backdropClosesModal={true}
|
|
||||||
onSelectImage={(index, image) => {
|
|
||||||
toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
|
||||||
}}
|
|
||||||
onClickImage={(props) => {
|
|
||||||
window.open(
|
|
||||||
props.target.src,
|
|
||||||
"_blank",
|
|
||||||
"toolbar=0,location=0,menubar=0"
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { Button, Form, Popover, Space } from "antd";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { getJobMedia } from "../../redux/media/media.actions";
|
|
||||||
import { selectAllMedia } from "../../redux/media/media.selectors";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import cleanAxios from "../../utils/CleanAxios";
|
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
allMedia: selectAllMedia,
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
getJobMedia: (id) => dispatch(getJobMedia(id)),
|
|
||||||
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(JobsDocumentsLocalGalleryReassign);
|
|
||||||
|
|
||||||
export function JobsDocumentsLocalGalleryReassign({
|
|
||||||
bodyshop,
|
|
||||||
jobid,
|
|
||||||
allMedia,
|
|
||||||
getJobMedia,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const handleFinish = async ({ jobid: newJobid }) => {
|
|
||||||
setLoading(true);
|
|
||||||
const selectedDocuments = allMedia[jobid].filter((m) => m.isSelected);
|
|
||||||
|
|
||||||
await cleanAxios.post(
|
|
||||||
`${bodyshop.localmediaserverhttp}/jobs/move`,
|
|
||||||
{
|
|
||||||
from_jobid: jobid,
|
|
||||||
jobid: newJobid,
|
|
||||||
files: selectedDocuments.map((f) => f.filename),
|
|
||||||
},
|
|
||||||
{ headers: { ims_token: bodyshop.localmediatoken } }
|
|
||||||
);
|
|
||||||
|
|
||||||
getJobMedia(jobid);
|
|
||||||
setVisible(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const popContent = (
|
|
||||||
<div>
|
|
||||||
<Form onFinish={handleFinish} layout="vertical" form={form}>
|
|
||||||
<Form.Item
|
|
||||||
label={t("documents.labels.newjobid")}
|
|
||||||
style={{ width: "20rem" }}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
name={"jobid"}
|
|
||||||
>
|
|
||||||
<JobSearchSelect />
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
<Space>
|
|
||||||
<Button type="primary" onClick={() => form.submit()}>
|
|
||||||
{t("general.actions.submit")}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setVisible(false)}>
|
|
||||||
{t("general.actions.cancel")}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover content={popContent} visible={visible}>
|
|
||||||
<Button
|
|
||||||
//disabled={selectedImages.length < 1}
|
|
||||||
onClick={() => setVisible(true)}
|
|
||||||
loading={loading}
|
|
||||||
>
|
|
||||||
{t("documents.actions.reassign")}
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Button, Space } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import {
|
|
||||||
selectAllmediaForJob,
|
|
||||||
deselectAllMediaForJob,
|
|
||||||
} from "../../redux/media/media.actions";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
selectAllmediaForJob: (jobid) => dispatch(selectAllmediaForJob(jobid)),
|
|
||||||
deselectAllmediaForJob: (jobid) => dispatch(deselectAllMediaForJob(jobid)),
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(JobsDocumentsLocalGallerySelectAllComponent);
|
|
||||||
|
|
||||||
export function JobsDocumentsLocalGallerySelectAllComponent({
|
|
||||||
jobid,
|
|
||||||
selectAllmediaForJob,
|
|
||||||
deselectAllmediaForJob,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// onSelectImage={(index, image) => {
|
|
||||||
// toggleMediaSelected({ jobid: job.id, filename: image.filename });
|
|
||||||
// }}
|
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
|
||||||
selectAllmediaForJob({ jobid });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeselectAll = () => {
|
|
||||||
deselectAllmediaForJob({ jobid });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space wrap>
|
|
||||||
<Button onClick={handleSelectAll}>
|
|
||||||
{t("general.actions.selectall")}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button onClick={handleDeselectAll}>
|
|
||||||
{t("general.actions.deselectall")}
|
|
||||||
</Button>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,6 @@ export function JobsExportAllButton({
|
|||||||
disabled,
|
disabled,
|
||||||
loadingCallback,
|
loadingCallback,
|
||||||
completedCallback,
|
completedCallback,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateJob] = useMutation(UPDATE_JOBS);
|
const [updateJob] = useMutation(UPDATE_JOBS);
|
||||||
@@ -40,7 +39,6 @@ export function JobsExportAllButton({
|
|||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||||
jobIds: jobIds,
|
jobIds: jobIds,
|
||||||
elgen: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let QbXmlResponse;
|
let QbXmlResponse;
|
||||||
@@ -85,7 +83,6 @@ export function JobsExportAllButton({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("PartnerResponse", PartnerResponse);
|
console.log("PartnerResponse", PartnerResponse);
|
||||||
const groupedData = _.groupBy(
|
const groupedData = _.groupBy(
|
||||||
PartnerResponse.data,
|
PartnerResponse.data,
|
||||||
@@ -109,70 +106,61 @@ export function JobsExportAllButton({
|
|||||||
});
|
});
|
||||||
//Call is not awaited as it is not critical to finish before proceeding.
|
//Call is not awaited as it is not critical to finish before proceeding.
|
||||||
});
|
});
|
||||||
|
await insertExportLog({
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
variables: {
|
||||||
//QBO Logs are handled server side.
|
logs: [
|
||||||
await insertExportLog({
|
{
|
||||||
variables: {
|
bodyshopid: bodyshop.id,
|
||||||
logs: [
|
jobid: key,
|
||||||
{
|
successful: false,
|
||||||
bodyshopid: bodyshop.id,
|
message: JSON.stringify(
|
||||||
jobid: key,
|
failedTransactions.map((ft) => ft.errorMessage)
|
||||||
successful: false,
|
),
|
||||||
message: JSON.stringify(
|
useremail: currentUser.email,
|
||||||
failedTransactions.map((ft) => ft.errorMessage)
|
|
||||||
),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
|
||||||
//QBO Logs are handled server side.
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
jobid: key,
|
|
||||||
successful: true,
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const jobUpdateResponse = await updateJob({
|
|
||||||
variables: {
|
|
||||||
jobIds: [key],
|
|
||||||
fields: {
|
|
||||||
status:
|
|
||||||
bodyshop.md_ro_statuses.default_exported || "Exported*",
|
|
||||||
date_exported: new Date(),
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await insertExportLog({
|
||||||
|
variables: {
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
jobid: key,
|
||||||
|
successful: true,
|
||||||
|
useremail: currentUser.email,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!jobUpdateResponse.errors) {
|
const jobUpdateResponse = await updateJob({
|
||||||
notification.open({
|
variables: {
|
||||||
type: "success",
|
jobIds: [key],
|
||||||
key: "jobsuccessexport",
|
fields: {
|
||||||
message: t("jobs.successes.exported"),
|
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||||
});
|
date_exported: new Date(),
|
||||||
} else {
|
},
|
||||||
notification["error"]({
|
},
|
||||||
message: t("jobs.errors.exporting", {
|
});
|
||||||
error: JSON.stringify(jobUpdateResponse.error),
|
|
||||||
}),
|
if (!jobUpdateResponse.errors) {
|
||||||
});
|
notification.open({
|
||||||
}
|
type: "success",
|
||||||
|
key: "jobsuccessexport",
|
||||||
|
message: t("jobs.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.exporting", {
|
||||||
|
error: JSON.stringify(jobUpdateResponse.error),
|
||||||
|
}),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
|
||||||
|
|
||||||
if (!!completedCallback) completedCallback([]);
|
if (!!completedCallback) completedCallback([]);
|
||||||
if (!!loadingCallback) loadingCallback(false);
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
|
|||||||
@@ -137,9 +137,9 @@ export function JobsList({ bodyshop }) {
|
|||||||
sortOrder:
|
sortOrder:
|
||||||
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.owner ? (
|
||||||
<Link
|
<Link
|
||||||
to={"/manage/owners/" + record.ownerid}
|
to={"/manage/owners/" + record.owner.id}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
<OwnerNameDisplay ownerObject={record} />
|
||||||
|
|||||||
@@ -61,9 +61,6 @@ export function JobNotesContainer({ jobId, insertAuditTrail }) {
|
|||||||
jobId={jobId}
|
jobId={jobId}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data={data ? data.jobs_by_pk.notes : null}
|
data={data ? data.jobs_by_pk.notes : null}
|
||||||
relatedRos={
|
|
||||||
data ? data.jobs_by_pk.vehicle && data.jobs_by_pk.vehicle.jobs : null
|
|
||||||
}
|
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
deleteLoading={deleteLoading}
|
deleteLoading={deleteLoading}
|
||||||
handleNoteDelete={handleNoteDelete}
|
handleNoteDelete={handleNoteDelete}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export function JobNotesComponent({
|
|||||||
setNoteUpsertContext,
|
setNoteUpsertContext,
|
||||||
deleteLoading,
|
deleteLoading,
|
||||||
ro_number,
|
ro_number,
|
||||||
relatedRos,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const Templates = TemplateList("job_special", {
|
const Templates = TemplateList("job_special", {
|
||||||
@@ -150,7 +149,6 @@ export function JobNotesComponent({
|
|||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
jobId: jobId,
|
jobId: jobId,
|
||||||
relatedRos: relatedRos,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,355 +0,0 @@
|
|||||||
import {
|
|
||||||
ExclamationCircleFilled,
|
|
||||||
PauseCircleOutlined,
|
|
||||||
SyncOutlined,
|
|
||||||
} from "@ant-design/icons";
|
|
||||||
import { useQuery } from "@apollo/client";
|
|
||||||
import { Button, Card, Grid, Input, Space, Table } from "antd";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import React, { useMemo, useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { Link, useHistory, useLocation } from "react-router-dom";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
||||||
import { alphaSort } from "../../utils/sorters";
|
|
||||||
import AlertComponent from "../alert/alert.component";
|
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
});
|
|
||||||
|
|
||||||
export function JobsReadyList({ bodyshop }) {
|
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
|
||||||
const { selected } = searchParams;
|
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
|
||||||
.filter((screen) => !!screen[1])
|
|
||||||
.slice(-1)[0];
|
|
||||||
|
|
||||||
const readyStatuses = useMemo(() => {
|
|
||||||
if (bodyshop.md_ro_statuses.ready_statuses)
|
|
||||||
return bodyshop.md_ro_statuses.ready_statuses;
|
|
||||||
|
|
||||||
return bodyshop.md_ro_statuses.post_production_statuses.filter(
|
|
||||||
(s) =>
|
|
||||||
s !== bodyshop.md_ro_statuses.default_invoiced &&
|
|
||||||
s !== bodyshop.md_ro_statuses.default_exported
|
|
||||||
);
|
|
||||||
}, [bodyshop.md_ro_statuses]);
|
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
|
|
||||||
variables: {
|
|
||||||
statuses: readyStatuses,
|
|
||||||
},
|
|
||||||
fetchPolicy: "network-only",
|
|
||||||
nextFetchPolicy: "network-only",
|
|
||||||
});
|
|
||||||
|
|
||||||
const [state, setState] = useState({
|
|
||||||
sortedInfo: {},
|
|
||||||
filteredInfo: { text: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const history = useHistory();
|
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
|
|
||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
|
||||||
|
|
||||||
const jobs = data
|
|
||||||
? searchText === ""
|
|
||||||
? data.jobs
|
|
||||||
: data.jobs.filter(
|
|
||||||
(j) =>
|
|
||||||
(j.ro_number || "")
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ownr_co_nm || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.comments || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ownr_fn || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ownr_ln || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.plate_no || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.v_model_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(j.v_make_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase())
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
|
||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
|
||||||
if (record) {
|
|
||||||
if (record.id) {
|
|
||||||
history.push({
|
|
||||||
search: queryString.stringify({
|
|
||||||
...searchParams,
|
|
||||||
selected: record.id,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<Space>
|
|
||||||
{record.ro_number || t("general.labels.na")}
|
|
||||||
{record.production_vars && record.production_vars.alert ? (
|
|
||||||
<ExclamationCircleFilled className="production-alert" />
|
|
||||||
) : null}
|
|
||||||
{record.suspended && (
|
|
||||||
<PauseCircleOutlined style={{ color: "orangered" }} />
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Link>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.owner"),
|
|
||||||
dataIndex: "owner",
|
|
||||||
key: "owner",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
responsive: ["md"],
|
|
||||||
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}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
<span>
|
|
||||||
<OwnerNameDisplay ownerObject={record} />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.ownr_ph1"),
|
|
||||||
dataIndex: "ownr_ph1",
|
|
||||||
key: "ownr_ph1",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
render: (text, record) => (
|
|
||||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.ownr_ph2"),
|
|
||||||
dataIndex: "ownr_ph2",
|
|
||||||
key: "ownr_ph2",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
render: (text, record) => (
|
|
||||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.status"),
|
|
||||||
dataIndex: "status",
|
|
||||||
key: "status",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
|
||||||
sortOrder:
|
|
||||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
|
||||||
filters:
|
|
||||||
(jobs &&
|
|
||||||
jobs
|
|
||||||
.map((j) => j.status)
|
|
||||||
.filter(onlyUnique)
|
|
||||||
.map((s) => {
|
|
||||||
return {
|
|
||||||
text: s || "No Status*",
|
|
||||||
value: [s],
|
|
||||||
};
|
|
||||||
})) ||
|
|
||||||
[],
|
|
||||||
onFilter: (value, record) => value.includes(record.status),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.vehicle"),
|
|
||||||
dataIndex: "vehicle",
|
|
||||||
key: "vehicle",
|
|
||||||
ellipsis: true,
|
|
||||||
render: (text, record) => {
|
|
||||||
return record.vehicleid ? (
|
|
||||||
<Link
|
|
||||||
to={"/manage/vehicles/" + record.vehicleid}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
>
|
|
||||||
{`${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("vehicles.fields.plate_no"),
|
|
||||||
dataIndex: "plate_no",
|
|
||||||
key: "plate_no",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
responsive: ["md"],
|
|
||||||
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
|
|
||||||
sortOrder:
|
|
||||||
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.clm_no"),
|
|
||||||
dataIndex: "clm_no",
|
|
||||||
key: "clm_no",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
|
|
||||||
sortOrder:
|
|
||||||
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
|
|
||||||
render: (text, record) =>
|
|
||||||
`${record.clm_no || ""}${
|
|
||||||
record.po_number ? ` (PO: ${record.po_number})` : ""
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.ins_co_nm"),
|
|
||||||
dataIndex: "ins_co_nm",
|
|
||||||
key: "ins_co_nm",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.clm_total"),
|
|
||||||
dataIndex: "clm_total",
|
|
||||||
key: "clm_total",
|
|
||||||
responsive: ["md"],
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
|
||||||
sortOrder:
|
|
||||||
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
|
|
||||||
render: (text, record) => (
|
|
||||||
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.comment"),
|
|
||||||
dataIndex: "comment",
|
|
||||||
key: "comment",
|
|
||||||
ellipsis: true,
|
|
||||||
responsive: ["md"],
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title: t("jobs.fields.owner_owing"),
|
|
||||||
// dataIndex: "owner_owing",
|
|
||||||
// key: "owner_owing",
|
|
||||||
// responsive: ["md"],
|
|
||||||
// render: (text, record) => (
|
|
||||||
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
|
||||||
// ),
|
|
||||||
// },
|
|
||||||
];
|
|
||||||
|
|
||||||
const scrollMapper = {
|
|
||||||
xs: true,
|
|
||||||
sm: true,
|
|
||||||
md: true,
|
|
||||||
lg: "100%",
|
|
||||||
xl: "100%",
|
|
||||||
xxl: "100%",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card
|
|
||||||
title={t("titles.bc.jobs-ready")}
|
|
||||||
extra={
|
|
||||||
<Space wrap>
|
|
||||||
<span>({readyStatuses && readyStatuses.join(", ")})</span>
|
|
||||||
<Button onClick={() => refetch()}>
|
|
||||||
<SyncOutlined />
|
|
||||||
</Button>
|
|
||||||
<Input.Search
|
|
||||||
placeholder={t("general.labels.search")}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchText(e.target.value);
|
|
||||||
}}
|
|
||||||
value={searchText}
|
|
||||||
enterButton
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table
|
|
||||||
loading={loading}
|
|
||||||
pagination={{ defaultPageSize: 50 }}
|
|
||||||
columns={columns}
|
|
||||||
rowKey="id"
|
|
||||||
dataSource={jobs}
|
|
||||||
scroll={{
|
|
||||||
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
|
|
||||||
}}
|
|
||||||
rowSelection={{
|
|
||||||
onSelect: (record) => {
|
|
||||||
handleOnRowClick(record);
|
|
||||||
},
|
|
||||||
selectedRowKeys: [selected],
|
|
||||||
type: "radio",
|
|
||||||
}}
|
|
||||||
onChange={handleTableChange}
|
|
||||||
onRow={(record, rowIndex) => {
|
|
||||||
return {
|
|
||||||
onClick: (event) => {
|
|
||||||
handleOnRowClick(record);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(JobsReadyList);
|
|
||||||
@@ -1,92 +1,51 @@
|
|||||||
import { Checkbox, Col, Form, Input, Row, Space, Switch, Tag } from "antd";
|
import { Col, Form, Input, Row, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
|
|
||||||
import NotesPresetButton from "../notes-preset-button/notes-preset-button.component";
|
import NotesPresetButton from "../notes-preset-button/notes-preset-button.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
export default function NoteUpsertModalComponent({ form }) {
|
||||||
noteUpsertModal: selectNoteUpsert,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(NoteUpsertModalComponent);
|
|
||||||
|
|
||||||
export function NoteUpsertModalComponent({ form, noteUpsertModal }) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { jobId, existingNote, relatedRos } = noteUpsertModal.context;
|
|
||||||
|
|
||||||
const filteredRelatedRos = relatedRos
|
|
||||||
? relatedRos.filter((j) => j.id !== jobId)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Row gutter={[16, 16]}>
|
||||||
<Row gutter={[16, 16]}>
|
<Col span={8}>
|
||||||
<Col span={8}>
|
<Form.Item
|
||||||
<Form.Item
|
label={t("notes.fields.critical")}
|
||||||
label={t("notes.fields.critical")}
|
name="critical"
|
||||||
name="critical"
|
valuePropName="checked"
|
||||||
valuePropName="checked"
|
>
|
||||||
>
|
<Switch />
|
||||||
<Switch />
|
</Form.Item>
|
||||||
</Form.Item>
|
</Col>
|
||||||
</Col>
|
<Col span={8}>
|
||||||
<Col span={8}>
|
<Form.Item
|
||||||
<Form.Item
|
label={t("notes.fields.private")}
|
||||||
label={t("notes.fields.private")}
|
name="private"
|
||||||
name="private"
|
valuePropName="checked"
|
||||||
valuePropName="checked"
|
>
|
||||||
>
|
<Switch />
|
||||||
<Switch />
|
</Form.Item>
|
||||||
</Form.Item>
|
</Col>
|
||||||
</Col>
|
<Col span={8}>
|
||||||
<Col span={8}>
|
<NotesPresetButton form={form} />
|
||||||
<NotesPresetButton form={form} />
|
</Col>
|
||||||
</Col>
|
<Col span={24}>
|
||||||
<Col span={24}>
|
<Form.Item
|
||||||
<Form.Item
|
label={t("notes.fields.text")}
|
||||||
label={t("notes.fields.text")}
|
name="text"
|
||||||
name="text"
|
rules={[
|
||||||
rules={[
|
{
|
||||||
{
|
required: true,
|
||||||
required: true,
|
//message: t("general.validation.required"),
|
||||||
//message: t("general.validation.required"),
|
},
|
||||||
},
|
]}
|
||||||
]}
|
>
|
||||||
>
|
<Input.TextArea
|
||||||
<Input.TextArea
|
rows={8}
|
||||||
rows={8}
|
placeholder={t("notes.labels.newnoteplaceholder")}
|
||||||
placeholder={t("notes.labels.newnoteplaceholder")}
|
/>
|
||||||
/>
|
</Form.Item>
|
||||||
</Form.Item>
|
</Col>
|
||||||
</Col>
|
</Row>
|
||||||
</Row>
|
|
||||||
<div>
|
|
||||||
<div>{!existingNote && t("notes.labels.addtorelatedro")}</div>
|
|
||||||
{!existingNote &&
|
|
||||||
filteredRelatedRos.map((j, idx) => (
|
|
||||||
<Space key={j.id} align="center">
|
|
||||||
<Form.Item
|
|
||||||
noStyle
|
|
||||||
name={["relatedros", j.id]}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Checkbox />
|
|
||||||
</Form.Item>
|
|
||||||
<Tag>
|
|
||||||
{`${j.ro_number || "N/A"}${j.clm_no ? ` | ${j.clm_no}` : ""}${
|
|
||||||
j.status ? ` | ${j.status}` : ""
|
|
||||||
}`}
|
|
||||||
</Tag>
|
|
||||||
</Space>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ import React, { useEffect } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
import { INSERT_NEW_NOTE, UPDATE_NOTE } from "../../graphql/notes.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
|
import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
|
||||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
import NoteUpsertModalComponent from "./note-upsert-modal.component";
|
import NoteUpsertModalComponent from "./note-upsert-modal.component";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -48,9 +48,7 @@ export function NoteUpsertModalContainer({
|
|||||||
}
|
}
|
||||||
}, [existingNote, form, visible]);
|
}, [existingNote, form, visible]);
|
||||||
|
|
||||||
const handleFinish = async (formValues) => {
|
const handleFinish = (values) => {
|
||||||
const { relatedros, ...values } = formValues;
|
|
||||||
|
|
||||||
if (existingNote) {
|
if (existingNote) {
|
||||||
logImEXEvent("job_note_update");
|
logImEXEvent("job_note_update");
|
||||||
|
|
||||||
@@ -72,44 +70,24 @@ export function NoteUpsertModalContainer({
|
|||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
} else {
|
} else {
|
||||||
logImEXEvent("job_note_insert");
|
logImEXEvent("job_note_insert");
|
||||||
const AdditionalNoteInserts = relatedros
|
|
||||||
? Object.keys(relatedros).filter((key) => relatedros[key])
|
|
||||||
: [];
|
|
||||||
|
|
||||||
await insertNote({
|
insertNote({
|
||||||
variables: {
|
variables: {
|
||||||
noteInput: [
|
noteInput: [
|
||||||
{ ...values, jobid: jobId, created_by: currentUser.email },
|
{ ...values, jobid: jobId, created_by: currentUser.email },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
}).then((r) => {
|
||||||
|
if (refetch) refetch();
|
||||||
if (AdditionalNoteInserts.length > 0) {
|
form.resetFields();
|
||||||
//Insert the others.
|
toggleModalVisible();
|
||||||
AdditionalNoteInserts.forEach(async (newJobId) => {
|
notification["success"]({
|
||||||
await insertNote({
|
message: t("notes.successes.create"),
|
||||||
variables: {
|
});
|
||||||
noteInput: [
|
insertAuditTrail({
|
||||||
{ ...values, jobid: newJobId, created_by: currentUser.email },
|
jobid: context.jobId,
|
||||||
],
|
operation: AuditTrailMapping.jobnoteadded(),
|
||||||
},
|
|
||||||
});
|
|
||||||
insertAuditTrail({
|
|
||||||
jobid: newJobId,
|
|
||||||
operation: AuditTrailMapping.jobnoteadded(),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (refetch) refetch();
|
|
||||||
form.resetFields();
|
|
||||||
toggleModalVisible();
|
|
||||||
notification["success"]({
|
|
||||||
message: t("notes.successes.create"),
|
|
||||||
});
|
|
||||||
insertAuditTrail({
|
|
||||||
jobid: context.jobId,
|
|
||||||
operation: AuditTrailMapping.jobnoteadded(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,17 +22,9 @@ export function NotesPresetButton({ bodyshop, form }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu
|
<Menu>
|
||||||
style={{
|
|
||||||
columnCount: Math.floor(bodyshop.md_notes_presets.length / 10) + 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{bodyshop.md_notes_presets.map((i, idx) => (
|
{bodyshop.md_notes_presets.map((i, idx) => (
|
||||||
<Menu.Item
|
<Menu.Item onClick={() => handleSelect(i)} onItemHover key={idx}>
|
||||||
onClick={() => handleSelect(i)}
|
|
||||||
key={idx}
|
|
||||||
style={{ breakInside: "avoid" }}
|
|
||||||
>
|
|
||||||
{i.label}
|
{i.label}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
|
|
||||||
<LayoutFormRow header={t("owners.forms.name")}>
|
<LayoutFormRow header={t("owners.forms.name")}>
|
||||||
<Form.Item label={t("owners.fields.ownr_title")} name="ownr_title">
|
<Form.Item label={t("owners.fields.ownr_title")} name="ownr_title">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -28,6 +29,7 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
<LayoutFormRow header={t("owners.forms.address")}>
|
<LayoutFormRow header={t("owners.forms.address")}>
|
||||||
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
||||||
<Input />
|
<Input />
|
||||||
@@ -48,6 +50,7 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
<LayoutFormRow header={t("owners.forms.contact")}>
|
<LayoutFormRow header={t("owners.forms.contact")}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("owners.fields.allow_text_message")}
|
label={t("owners.fields.allow_text_message")}
|
||||||
@@ -95,9 +98,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Form.Item label={t("owners.fields.note")} name="note">
|
|
||||||
<Input.TextArea rows={4} />
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,14 +59,6 @@ export default function OwnerFindModalComponent({
|
|||||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("owners.fields.note"),
|
|
||||||
dataIndex: "note",
|
|
||||||
key: "note",
|
|
||||||
render: (text, record) => (
|
|
||||||
<span style={{ whiteSpace: "pre" }}>{record.note}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleOnRowClick = (record) => {
|
const handleOnRowClick = (record) => {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function OwnerFindModalContainer({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (modalProps.visible && owner) {
|
if (modalProps.visible && owner) {
|
||||||
const s = OwnerNameDisplayFunction(owner, true);
|
const s = OwnerNameDisplayFunction(owner);
|
||||||
|
|
||||||
setSearchText(s.trim());
|
setSearchText(s.trim());
|
||||||
callSearchowners({ variables: { search: s.trim() } });
|
callSearchowners({ variables: { search: s.trim() } });
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export function OwnerNameDisplay({ bodyshop, ownerObject }) {
|
|||||||
}`.trim();
|
}`.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
export function OwnerNameDisplayFunction(ownerObject) {
|
||||||
const emptyTest =
|
const emptyTest =
|
||||||
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
|
|||||||
|
|
||||||
const rdxStore = store.getState();
|
const rdxStore = store.getState();
|
||||||
|
|
||||||
if (rdxStore.user.bodyshop.last_name_first && !forceFirstLast)
|
if (rdxStore.user.bodyshop.last_name_first)
|
||||||
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
|
||||||
ownerObject.ownr_co_nm || ""
|
ownerObject.ownr_co_nm || ""
|
||||||
}`.trim();
|
}`.trim();
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Checkbox, notification, Space, Spin } from "antd";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { MUTATION_UPDATE_PO_CM_REECEIVED } from "../../graphql/parts-orders.queries";
|
|
||||||
|
|
||||||
export default function PartsOrderCmReceived({
|
|
||||||
checked,
|
|
||||||
orderLineId,
|
|
||||||
partsOrderId,
|
|
||||||
}) {
|
|
||||||
const [updateLine] = useMutation(MUTATION_UPDATE_PO_CM_REECEIVED);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
const handleChange = async (e) => {
|
|
||||||
setLoading(true);
|
|
||||||
const result = await updateLine({
|
|
||||||
variables: {
|
|
||||||
partsLineId: orderLineId,
|
|
||||||
partsOrder: { cm_received: e.target.checked },
|
|
||||||
},
|
|
||||||
update(cache) {
|
|
||||||
cache.modify({
|
|
||||||
id: cache.identify({
|
|
||||||
id: partsOrderId,
|
|
||||||
__typename: "parts_orders",
|
|
||||||
}),
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
parts_order_lines(ex, { readField }) {
|
|
||||||
console.log(ex);
|
|
||||||
return ex.map((lineref) => {
|
|
||||||
if (orderLineId.id !== readField("id", lineref)) {
|
|
||||||
lineref.cm_received = e.target.checked;
|
|
||||||
}
|
|
||||||
return lineref;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!!!result.errors) {
|
|
||||||
notification["success"]({
|
|
||||||
message: t("parts_orders.successes.line_updated"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("parts_orders.errors.saving", {
|
|
||||||
error: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space>
|
|
||||||
<Checkbox checked={checked} onChange={handleChange} />
|
|
||||||
{loading && <Spin size="small" />}
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { Button, Popconfirm } from "antd";
|
|
||||||
import { DeleteFilled } from "@ant-design/icons";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { DELETE_PARTS_ORDER_LINE } from "../../graphql/parts-orders.queries";
|
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
|
|
||||||
export default function PartsOrderDeleteLine({
|
|
||||||
disabled,
|
|
||||||
partsLineId,
|
|
||||||
partsOrderId,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [deletePartsOrderLine] = useMutation(DELETE_PARTS_ORDER_LINE);
|
|
||||||
return (
|
|
||||||
<Popconfirm
|
|
||||||
title={t("parts_orders.labels.confirmdelete")}
|
|
||||||
disabled={disabled}
|
|
||||||
onConfirm={async () => {
|
|
||||||
//Delete the parts return.!
|
|
||||||
|
|
||||||
await deletePartsOrderLine({
|
|
||||||
variables: { partsOrderLineId: partsLineId },
|
|
||||||
update(cache) {
|
|
||||||
cache.modify({
|
|
||||||
id: cache.identify({
|
|
||||||
__typename: "parts_orders",
|
|
||||||
id: partsOrderId,
|
|
||||||
}),
|
|
||||||
fields: {
|
|
||||||
parts_order_lines(cached, { readField }) {
|
|
||||||
return cached.filter((c) => {
|
|
||||||
return readField("id", c) !== partsLineId;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button disabled={disabled}>
|
|
||||||
<DeleteFilled />
|
|
||||||
</Button>
|
|
||||||
</Popconfirm>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -29,8 +29,6 @@ import { alphaSort } from "../../utils/sorters";
|
|||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
|
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
|
||||||
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
|
|
||||||
import PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.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";
|
||||||
@@ -79,7 +77,6 @@ export function PartsOrderListTableComponent({
|
|||||||
});
|
});
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const selectedpartsorder = search.partsorderid;
|
const selectedpartsorder = search.partsorderid;
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
|
|
||||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||||
|
|
||||||
@@ -349,23 +346,6 @@ export function PartsOrderListTableComponent({
|
|||||||
dataIndex: "status",
|
dataIndex: "status",
|
||||||
key: "status",
|
key: "status",
|
||||||
},
|
},
|
||||||
|
|
||||||
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
title: t("parts_orders.fields.cm_received"),
|
|
||||||
dataIndex: "cm_received",
|
|
||||||
key: "cm_received",
|
|
||||||
render: (text, record) => (
|
|
||||||
<PartsOrderCmReceived
|
|
||||||
orderLineId={record.id}
|
|
||||||
checked={record.cm_received}
|
|
||||||
partsorderid={selectedPartsOrderRecord.id}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
{
|
||||||
title: t("parts_orders.fields.backordered_on"),
|
title: t("parts_orders.fields.backordered_on"),
|
||||||
dataIndex: "backordered_on",
|
dataIndex: "backordered_on",
|
||||||
@@ -392,21 +372,12 @@ export function PartsOrderListTableComponent({
|
|||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space wrap>
|
<PartsOrderLineBackorderButton
|
||||||
<PartsOrderDeleteLine
|
disabled={jobRO}
|
||||||
disabled={jobRO}
|
partsOrderStatus={record.status}
|
||||||
partsOrderStatus={record.status}
|
partsLineId={record.id}
|
||||||
partsLineId={record.id}
|
jobLineId={record.job_line_id}
|
||||||
partsOrderId={selectedpartsorder}
|
/>
|
||||||
jobLineId={record.job_line_id}
|
|
||||||
/>
|
|
||||||
<PartsOrderLineBackorderButton
|
|
||||||
disabled={jobRO}
|
|
||||||
partsOrderStatus={record.status}
|
|
||||||
partsLineId={record.id}
|
|
||||||
jobLineId={record.job_line_id}
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -432,21 +403,6 @@ export function PartsOrderListTableComponent({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredPartsOrders = parts_orders
|
|
||||||
? searchText === ""
|
|
||||||
? parts_orders
|
|
||||||
: parts_orders.filter(
|
|
||||||
(b) =>
|
|
||||||
(b.order_number || "")
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(b.vendor.name || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase())
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={t("parts_orders.labels.parts_orders")}
|
title={t("parts_orders.labels.parts_orders")}
|
||||||
@@ -457,10 +413,8 @@ export function PartsOrderListTableComponent({
|
|||||||
</Button>
|
</Button>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
value={searchText}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setSearchText(e.target.value);
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
@@ -484,7 +438,7 @@ export function PartsOrderListTableComponent({
|
|||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={filteredPartsOrders}
|
dataSource={parts_orders}
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
Menu,
|
Menu,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
Checkbox,
|
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -115,15 +114,6 @@ export function PartsOrderModalComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
{!isReturn && (
|
|
||||||
<Form.Item
|
|
||||||
name="removefrompartsqueue"
|
|
||||||
label={t("parts_orders.labels.removefrompartsqueue")}
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Checkbox />
|
|
||||||
</Form.Item>
|
|
||||||
)}
|
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Divider orientation="left">
|
<Divider orientation="left">
|
||||||
{t("parts_orders.labels.inthisorder")}
|
{t("parts_orders.labels.inthisorder")}
|
||||||
@@ -290,7 +280,6 @@ export function PartsOrderModalComponent({
|
|||||||
>
|
>
|
||||||
<Input.TextArea rows={3} />
|
<Input.TextArea rows={3} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Radio.Group
|
<Radio.Group
|
||||||
defaultValue={sendType}
|
defaultValue={sendType}
|
||||||
onChange={(e) => setSendType(e.target.value)}
|
onChange={(e) => setSendType(e.target.value)}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import PartsOrderModalComponent from "./parts-order-modal.component";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser,
|
currentUser: selectCurrentUser,
|
||||||
@@ -91,9 +90,8 @@ export function PartsOrderModalContainer({
|
|||||||
|
|
||||||
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
|
const [insertPartOrder] = useMutation(INSERT_NEW_PARTS_ORDERS);
|
||||||
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
|
const [updateJobLines] = useMutation(UPDATE_JOB_LINE_STATUS);
|
||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
|
||||||
|
|
||||||
const handleFinish = async ({ removefrompartsqueue, ...values }) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("parts_order_insert");
|
logImEXEvent("parts_order_insert");
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
const insertResult = await insertPartOrder({
|
const insertResult = await insertPartOrder({
|
||||||
@@ -101,7 +99,6 @@ export function PartsOrderModalContainer({
|
|||||||
po: [
|
po: [
|
||||||
{
|
{
|
||||||
...values,
|
...values,
|
||||||
order_date: moment().format("YYYY-MM-DD"),
|
|
||||||
orderedby: currentUser.email,
|
orderedby: currentUser.email,
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
user_email: currentUser.email,
|
user_email: currentUser.email,
|
||||||
@@ -131,17 +128,6 @@ export function PartsOrderModalContainer({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!isReturn && removefrompartsqueue) {
|
|
||||||
await updateJob({
|
|
||||||
variables: {
|
|
||||||
jobId: jobId,
|
|
||||||
job: {
|
|
||||||
queued_for_parts: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: jobId,
|
jobid: jobId,
|
||||||
operation: isReturn
|
operation: isReturn
|
||||||
@@ -319,7 +305,6 @@ export function PartsOrderModalContainer({
|
|||||||
quantity: value.part_qty,
|
quantity: value.part_qty,
|
||||||
job_line_id: isReturn ? value.joblineid : value.id,
|
job_line_id: isReturn ? value.joblineid : value.id,
|
||||||
part_type: value.part_type,
|
part_type: value.part_type,
|
||||||
...(isReturn && { cm_received: false }),
|
|
||||||
});
|
});
|
||||||
return acc;
|
return acc;
|
||||||
}, [])
|
}, [])
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Form, Modal, notification } from "antd";
|
import { Form, Modal, notification } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -31,7 +31,7 @@ export function PartsReceiveModalContainer({
|
|||||||
bodyshop,
|
bodyshop,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const { visible, context, actions } = partsReceiveModal;
|
const { visible, context, actions } = partsReceiveModal;
|
||||||
const { partsorderlines } = context;
|
const { partsorderlines } = context;
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export function PartsReceiveModalContainer({
|
|||||||
|
|
||||||
const handleFinish = async (values) => {
|
const handleFinish = async (values) => {
|
||||||
logImEXEvent("parts_order_receive");
|
logImEXEvent("parts_order_receive");
|
||||||
setLoading(true);
|
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
values.partsorderlines.map((li) => {
|
values.partsorderlines.map((li) => {
|
||||||
return receivePartsLine({
|
return receivePartsLine({
|
||||||
@@ -75,7 +75,7 @@ export function PartsReceiveModalContainer({
|
|||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("parts_orders.successes.received"),
|
message: t("parts_orders.successes.received"),
|
||||||
});
|
});
|
||||||
setLoading(false);
|
|
||||||
if (refetch) refetch();
|
if (refetch) refetch();
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
};
|
};
|
||||||
@@ -96,7 +96,6 @@ export function PartsReceiveModalContainer({
|
|||||||
title={t("parts_orders.labels.receive")}
|
title={t("parts_orders.labels.receive")}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
okButtonProps={{ loading: loading }}
|
|
||||||
destroyOnClose
|
destroyOnClose
|
||||||
forceRender
|
forceRender
|
||||||
width="50%"
|
width="50%"
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ export function PayableExportAll({
|
|||||||
disabled,
|
disabled,
|
||||||
loadingCallback,
|
loadingCallback,
|
||||||
completedCallback,
|
completedCallback,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateBill] = useMutation(UPDATE_BILLS);
|
const [updateBill] = useMutation(UPDATE_BILLS);
|
||||||
@@ -43,7 +42,6 @@ export function PayableExportAll({
|
|||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/payables`, {
|
PartnerResponse = await axios.post(`/qbo/payables`, {
|
||||||
bills: billids,
|
bills: billids,
|
||||||
elgen: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let QbXmlResponse;
|
let QbXmlResponse;
|
||||||
@@ -106,62 +104,57 @@ export function PayableExportAll({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
|
||||||
//QBO Logs are handled server side.
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
billid: key,
|
|
||||||
successful: false,
|
|
||||||
message: JSON.stringify(
|
|
||||||
failedTransactions.map((ft) => ft.errorMessage)
|
|
||||||
),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
|
||||||
//QBO Logs are handled server side.
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
billid: key,
|
|
||||||
successful: true,
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const billUpdateResponse = await updateBill({
|
await insertExportLog({
|
||||||
variables: {
|
variables: {
|
||||||
billIdList: [key],
|
logs: [
|
||||||
bill: {
|
{
|
||||||
exported: true,
|
bodyshopid: bodyshop.id,
|
||||||
exported_at: new Date(),
|
billid: key,
|
||||||
|
successful: false,
|
||||||
|
message: JSON.stringify(
|
||||||
|
failedTransactions.map((ft) => ft.errorMessage)
|
||||||
|
),
|
||||||
|
useremail: currentUser.email,
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await insertExportLog({
|
||||||
|
variables: {
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
billid: key,
|
||||||
|
successful: true,
|
||||||
|
useremail: currentUser.email,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const billUpdateResponse = await updateBill({
|
||||||
|
variables: {
|
||||||
|
billIdList: [key],
|
||||||
|
bill: {
|
||||||
|
exported: true,
|
||||||
|
exported_at: new Date(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!!!billUpdateResponse.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
key: "billsuccessexport",
|
||||||
|
message: t("bills.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("bills.errors.exporting", {
|
||||||
|
error: JSON.stringify(billUpdateResponse.error),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!!!billUpdateResponse.errors) {
|
|
||||||
notification.open({
|
|
||||||
type: "success",
|
|
||||||
key: "billsuccessexport",
|
|
||||||
message: t("bills.successes.exported"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bills.errors.exporting", {
|
|
||||||
error: JSON.stringify(billUpdateResponse.error),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@@ -171,8 +164,6 @@ export function PayableExportAll({
|
|||||||
await Promise.all(proms);
|
await Promise.all(proms);
|
||||||
if (!!completedCallback) completedCallback([]);
|
if (!!completedCallback) completedCallback([]);
|
||||||
if (!!loadingCallback) loadingCallback(false);
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export function PayableExportButton({
|
|||||||
disabled,
|
disabled,
|
||||||
loadingCallback,
|
loadingCallback,
|
||||||
setSelectedBills,
|
setSelectedBills,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateBill] = useMutation(UPDATE_BILLS);
|
const [updateBill] = useMutation(UPDATE_BILLS);
|
||||||
@@ -44,7 +43,6 @@ export function PayableExportButton({
|
|||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/payables`, {
|
PartnerResponse = await axios.post(`/qbo/payables`, {
|
||||||
bills: [billId],
|
bills: [billId],
|
||||||
elgen: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//Default is QBD
|
//Default is QBD
|
||||||
@@ -102,72 +100,64 @@ export function PayableExportButton({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
await insertExportLog({
|
||||||
//QBO Logs are handled server side.
|
variables: {
|
||||||
await insertExportLog({
|
logs: [
|
||||||
variables: {
|
{
|
||||||
logs: [
|
bodyshopid: bodyshop.id,
|
||||||
{
|
billid: billId,
|
||||||
bodyshopid: bodyshop.id,
|
successful: false,
|
||||||
billid: billId,
|
message: JSON.stringify(
|
||||||
successful: false,
|
failedTransactions.map((ft) => ft.errorMessage)
|
||||||
message: JSON.stringify(
|
),
|
||||||
failedTransactions.map((ft) => ft.errorMessage)
|
useremail: currentUser.email,
|
||||||
),
|
},
|
||||||
useremail: currentUser.email,
|
],
|
||||||
},
|
},
|
||||||
],
|
});
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (successfulTransactions.length > 0) {
|
if (successfulTransactions.length > 0) {
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
await insertExportLog({
|
||||||
//QBO Logs are handled server side.
|
variables: {
|
||||||
await insertExportLog({
|
logs: [
|
||||||
variables: {
|
{
|
||||||
logs: [
|
bodyshopid: bodyshop.id,
|
||||||
{
|
billid: billId,
|
||||||
bodyshopid: bodyshop.id,
|
successful: true,
|
||||||
billid: billId,
|
useremail: currentUser.email,
|
||||||
successful: true,
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const billUpdateResponse = await updateBill({
|
|
||||||
variables: {
|
|
||||||
billIdList: successfulTransactions.map(
|
|
||||||
(st) =>
|
|
||||||
st[
|
|
||||||
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
|
||||||
? "billid"
|
|
||||||
: "id"
|
|
||||||
]
|
|
||||||
),
|
|
||||||
bill: {
|
|
||||||
exported: true,
|
|
||||||
exported_at: new Date(),
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const billUpdateResponse = await updateBill({
|
||||||
|
variables: {
|
||||||
|
billIdList: successfulTransactions.map(
|
||||||
|
(st) =>
|
||||||
|
st[
|
||||||
|
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
||||||
|
? "billid"
|
||||||
|
: "id"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
bill: {
|
||||||
|
exported: true,
|
||||||
|
exported_at: new Date(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!!!billUpdateResponse.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
key: "billsuccessexport",
|
||||||
|
message: t("bills.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("bills.errors.exporting", {
|
||||||
|
error: JSON.stringify(billUpdateResponse.error),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!!!billUpdateResponse.errors) {
|
|
||||||
notification.open({
|
|
||||||
type: "success",
|
|
||||||
key: "billsuccessexport",
|
|
||||||
message: t("bills.successes.exported"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bills.errors.exporting", {
|
|
||||||
error: JSON.stringify(billUpdateResponse.error),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
|
||||||
|
|
||||||
if (setSelectedBills) {
|
if (setSelectedBills) {
|
||||||
setSelectedBills((selectedBills) => {
|
setSelectedBills((selectedBills) => {
|
||||||
return selectedBills.filter((i) => i !== billId);
|
return selectedBills.filter((i) => i !== billId);
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
import { gql, useMutation } from "@apollo/client";
|
|
||||||
import { Button, notification } from "antd";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
|
||||||
import {
|
|
||||||
selectBodyshop,
|
|
||||||
selectCurrentUser,
|
|
||||||
} from "../../redux/user/user.selectors";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
currentUser: selectCurrentUser,
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(BillMarkSelectedExported);
|
|
||||||
|
|
||||||
export function BillMarkSelectedExported({
|
|
||||||
bodyshop,
|
|
||||||
currentUser,
|
|
||||||
billids,
|
|
||||||
disabled,
|
|
||||||
loadingCallback,
|
|
||||||
completedCallback,
|
|
||||||
refetch,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
|
||||||
const [updateBill] = useMutation(gql`
|
|
||||||
mutation UPDATE_BILL($billIds: [uuid!]!) {
|
|
||||||
update_bills(where: { id: { _in: $billIds } }, _set: { exported: true }) {
|
|
||||||
returning {
|
|
||||||
id
|
|
||||||
exported
|
|
||||||
exported_at
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
const handleUpdate = async () => {
|
|
||||||
setLoading(true);
|
|
||||||
loadingCallback(true);
|
|
||||||
const result = await updateBill({
|
|
||||||
variables: { billIds: billids },
|
|
||||||
update(cache) {},
|
|
||||||
});
|
|
||||||
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: billids.map((id) => {
|
|
||||||
return {
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
billid: id,
|
|
||||||
successful: true,
|
|
||||||
message: JSON.stringify([t("general.labels.markedexported")]),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result.errors) {
|
|
||||||
notification["success"]({
|
|
||||||
message: t("bills.successes.markexported"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bills.errors.saving", {
|
|
||||||
error: JSON.stringify(result.errors),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
loadingCallback(false);
|
|
||||||
completedCallback && completedCallback([]);
|
|
||||||
setLoading(false);
|
|
||||||
refetch && refetch();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button loading={loading} disabled={disabled} onClick={handleUpdate}>
|
|
||||||
{t("bills.labels.markexported")}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -26,7 +26,6 @@ export function PaymentExportButton({
|
|||||||
disabled,
|
disabled,
|
||||||
loadingCallback,
|
loadingCallback,
|
||||||
setSelectedPayments,
|
setSelectedPayments,
|
||||||
refetch,
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
||||||
@@ -41,7 +40,6 @@ export function PaymentExportButton({
|
|||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/payments`, {
|
PartnerResponse = await axios.post(`/qbo/payments`, {
|
||||||
payments: [paymentId],
|
payments: [paymentId],
|
||||||
elgen: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//Default is QBD
|
//Default is QBD
|
||||||
@@ -102,68 +100,63 @@ export function PaymentExportButton({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
|
||||||
//QBO Logs are handled server side.
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
paymentid: paymentId,
|
|
||||||
successful: false,
|
|
||||||
message: JSON.stringify(
|
|
||||||
failedTransactions.map((ft) => ft.errorMessage)
|
|
||||||
),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
|
||||||
//QBO Logs are handled server side.
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
paymentid: paymentId,
|
|
||||||
successful: true,
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const paymentUpdateResponse = await updatePayment({
|
await insertExportLog({
|
||||||
variables: {
|
variables: {
|
||||||
paymentIdList: successfulTransactions.map(
|
logs: [
|
||||||
(st) =>
|
{
|
||||||
st[
|
bodyshopid: bodyshop.id,
|
||||||
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
paymentid: paymentId,
|
||||||
? "paymentid"
|
successful: false,
|
||||||
: "id"
|
message: JSON.stringify(
|
||||||
]
|
failedTransactions.map((ft) => ft.errorMessage)
|
||||||
),
|
),
|
||||||
payment: {
|
useremail: currentUser.email,
|
||||||
exportedat: new Date(),
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await insertExportLog({
|
||||||
|
variables: {
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
paymentid: paymentId,
|
||||||
|
successful: true,
|
||||||
|
useremail: currentUser.email,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const paymentUpdateResponse = await updatePayment({
|
||||||
|
variables: {
|
||||||
|
paymentIdList: successfulTransactions.map(
|
||||||
|
(st) =>
|
||||||
|
st[
|
||||||
|
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
||||||
|
? "paymentid"
|
||||||
|
: "id"
|
||||||
|
]
|
||||||
|
),
|
||||||
|
payment: {
|
||||||
|
exportedat: new Date(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!!!paymentUpdateResponse.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
key: "paymentsuccessexport",
|
||||||
|
message: t("payments.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting", {
|
||||||
|
error: JSON.stringify(paymentUpdateResponse.error),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!!!paymentUpdateResponse.errors) {
|
|
||||||
notification.open({
|
|
||||||
type: "success",
|
|
||||||
key: "paymentsuccessexport",
|
|
||||||
message: t("payments.successes.exported"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("payments.errors.exporting", {
|
|
||||||
error: JSON.stringify(paymentUpdateResponse.error),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setSelectedPayments) {
|
if (setSelectedPayments) {
|
||||||
@@ -172,7 +165,7 @@ export function PaymentExportButton({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
|
||||||
if (!!loadingCallback) loadingCallback(false);
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export function PaymentsExportAllButton({
|
|||||||
disabled,
|
disabled,
|
||||||
loadingCallback,
|
loadingCallback,
|
||||||
completedCallback,
|
completedCallback,
|
||||||
refetch
|
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updatePayments] = useMutation(UPDATE_PAYMENTS);
|
const [updatePayments] = useMutation(UPDATE_PAYMENTS);
|
||||||
@@ -39,7 +38,6 @@ export function PaymentsExportAllButton({
|
|||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/payments`, {
|
PartnerResponse = await axios.post(`/qbo/payments`, {
|
||||||
payments: paymentIds,
|
payments: paymentIds,
|
||||||
elgen: true,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let QbXmlResponse;
|
let QbXmlResponse;
|
||||||
@@ -94,61 +92,54 @@ export function PaymentsExportAllButton({
|
|||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
await insertExportLog({
|
||||||
//QBO Logs are handled server side.
|
variables: {
|
||||||
await insertExportLog({
|
logs: [
|
||||||
variables: {
|
{
|
||||||
logs: [
|
bodyshopid: bodyshop.id,
|
||||||
{
|
paymentid: key,
|
||||||
bodyshopid: bodyshop.id,
|
successful: false,
|
||||||
paymentid: key,
|
message: JSON.stringify(
|
||||||
successful: false,
|
failedTransactions.map((ft) => ft.errorMessage)
|
||||||
message: JSON.stringify(
|
),
|
||||||
failedTransactions.map((ft) => ft.errorMessage)
|
useremail: currentUser.email,
|
||||||
),
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!(bodyshop.accountingconfig && bodyshop.accountingconfig.qbo)) {
|
|
||||||
//QBO Logs are handled server side.
|
|
||||||
|
|
||||||
await insertExportLog({
|
|
||||||
variables: {
|
|
||||||
logs: [
|
|
||||||
{
|
|
||||||
bodyshopid: bodyshop.id,
|
|
||||||
paymentid: key,
|
|
||||||
successful: true,
|
|
||||||
useremail: currentUser.email,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const paymentUpdateResponse = await updatePayments({
|
|
||||||
variables: {
|
|
||||||
paymentIdList: [key],
|
|
||||||
payment: {
|
|
||||||
exportedat: new Date(),
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await insertExportLog({
|
||||||
|
variables: {
|
||||||
|
logs: [
|
||||||
|
{
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
paymentid: key,
|
||||||
|
successful: true,
|
||||||
|
useremail: currentUser.email,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const paymentUpdateResponse = await updatePayments({
|
||||||
|
variables: {
|
||||||
|
paymentIdList: [key],
|
||||||
|
payment: {
|
||||||
|
exportedat: new Date(),
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!!!paymentUpdateResponse.errors) {
|
||||||
|
notification.open({
|
||||||
|
type: "success",
|
||||||
|
key: "paymentsuccessexport",
|
||||||
|
message: t("payments.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting", {
|
||||||
|
error: JSON.stringify(paymentUpdateResponse.error),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
if (!!!paymentUpdateResponse.errors) {
|
|
||||||
notification.open({
|
|
||||||
type: "success",
|
|
||||||
key: "paymentsuccessexport",
|
|
||||||
message: t("payments.successes.exported"),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("payments.errors.exporting", {
|
|
||||||
error: JSON.stringify(paymentUpdateResponse.error),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@@ -157,7 +148,6 @@ export function PaymentsExportAllButton({
|
|||||||
await Promise.all(proms);
|
await Promise.all(proms);
|
||||||
if (!!completedCallback) completedCallback([]);
|
if (!!completedCallback) completedCallback([]);
|
||||||
if (!!loadingCallback) loadingCallback(false);
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) refetch();
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
CalendarOutlined,
|
CalendarOutlined,
|
||||||
EyeFilled,
|
EyeFilled,
|
||||||
DownloadOutlined,
|
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space } from "antd";
|
import { Card, Col, Row, Space } from "antd";
|
||||||
@@ -15,7 +14,6 @@ import ProductionSubletsManageComponent from "../production-sublets-manage/produ
|
|||||||
import "./production-board-card.styles.scss";
|
import "./production-board-card.styles.scss";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
|
||||||
|
|
||||||
export default function ProductionBoardCard(
|
export default function ProductionBoardCard(
|
||||||
technician,
|
technician,
|
||||||
@@ -159,16 +157,6 @@ export default function ProductionBoardCard(
|
|||||||
</Row>
|
</Row>
|
||||||
</Col>
|
</Col>
|
||||||
)} */}
|
)} */}
|
||||||
{cardSettings && cardSettings.actual_in && card.actual_in && (
|
|
||||||
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
|
|
||||||
<Space>
|
|
||||||
<DownloadOutlined />
|
|
||||||
<DateTimeFormatter format="MM/DD">
|
|
||||||
{card.actual_in}
|
|
||||||
</DateTimeFormatter>
|
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
{cardSettings &&
|
{cardSettings &&
|
||||||
cardSettings.scheduled_completion &&
|
cardSettings.scheduled_completion &&
|
||||||
card.scheduled_completion && (
|
card.scheduled_completion && (
|
||||||
@@ -200,11 +188,6 @@ export default function ProductionBoardCard(
|
|||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{cardSettings && cardSettings.partsstatus && (
|
|
||||||
<Col span={24}>
|
|
||||||
<JobPartsQueueCount parts={card.joblines_status} />
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -97,13 +97,6 @@ export default function ProductionBoardKanbanCardSettings({
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
valuePropName="checked"
|
|
||||||
label={t("production.labels.actual_in")}
|
|
||||||
name="actual_in"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
</Col>
|
||||||
<Col span={12}>
|
<Col span={12}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@@ -138,13 +131,6 @@ export default function ProductionBoardKanbanCardSettings({
|
|||||||
>
|
>
|
||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
valuePropName="checked"
|
|
||||||
label={t("production.labels.partsstatus")}
|
|
||||||
name="partsstatus"
|
|
||||||
>
|
|
||||||
<Switch />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
label={t("production.labels.stickyheader")}
|
label={t("production.labels.stickyheader")}
|
||||||
|
|||||||
@@ -51,10 +51,7 @@ export function ProductionBoardKanbanComponent({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const boardData = createBoardData(
|
const boardData = createBoardData(
|
||||||
[
|
bodyshop.md_ro_statuses.production_statuses,
|
||||||
...bodyshop.md_ro_statuses.production_statuses,
|
|
||||||
...(bodyshop.md_ro_statuses.additional_board_statuses || []),
|
|
||||||
],
|
|
||||||
data,
|
data,
|
||||||
filter
|
filter
|
||||||
);
|
);
|
||||||
@@ -64,7 +61,13 @@ export function ProductionBoardKanbanComponent({
|
|||||||
});
|
});
|
||||||
setBoardLanes(boardData);
|
setBoardLanes(boardData);
|
||||||
setIsMoving(false);
|
setIsMoving(false);
|
||||||
}, [data, setBoardLanes, setIsMoving, bodyshop.md_ro_statuses, filter]);
|
}, [
|
||||||
|
data,
|
||||||
|
setBoardLanes,
|
||||||
|
setIsMoving,
|
||||||
|
bodyshop.md_ro_statuses.production_statuses,
|
||||||
|
filter,
|
||||||
|
]);
|
||||||
|
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
|
|||||||
@@ -42,24 +42,17 @@ export function ProductionColumnsComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const columnKeys = columns.map((i) => i.key);
|
const columnKeys = columns.map((i) => i.key);
|
||||||
const cols = dataSource({
|
|
||||||
technician,
|
|
||||||
state: tableState,
|
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
|
||||||
});
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu
|
<Menu onClick={handleAdd}>
|
||||||
onClick={handleAdd}
|
{dataSource({
|
||||||
style={{
|
technician,
|
||||||
columnCount: Math.max(Math.floor(cols.length / 10), 1),
|
state: tableState,
|
||||||
}}
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
>
|
})
|
||||||
{cols
|
|
||||||
.filter((i) => !columnKeys.includes(i.key))
|
.filter((i) => !columnKeys.includes(i.key))
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<Menu.Item key={item.key} style={{ breakInside: "avoid" }}>
|
<Menu.Item key={item.key}>{item.title}</Menu.Item>
|
||||||
{item.title}
|
|
||||||
</Menu.Item>
|
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Icon from "@ant-design/icons";
|
import Icon from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Input, Popover, Tooltip } from "antd";
|
import { Button, Input, Popover } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FaRegStickyNote } from "react-icons/fa";
|
import { FaRegStickyNote } from "react-icons/fa";
|
||||||
@@ -69,11 +69,10 @@ export default function ProductionListColumnComment({ record }) {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
textOverflow: "ellipsis",
|
textOverflow: "ellipsis",
|
||||||
display: "inline-block",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon component={FaRegStickyNote} style={{ marginRight: ".2rem" }} />
|
<Icon component={FaRegStickyNote} style={{ marginRight: ".2rem" }} />
|
||||||
<Tooltip title={record.comment}>{record.comment || " "}</Tooltip>
|
{record.comment || " "}
|
||||||
</div>
|
</div>
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,26 +5,24 @@ import moment from "moment";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { TimeFormatter } from "../../utils/DateFormatter";
|
|
||||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
||||||
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
||||||
import ProductionListColumnAlert from "./production-list-columns.alert.component";
|
import ProductionListColumnAlert from "./production-list-columns.alert.component";
|
||||||
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
|
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
|
||||||
import ProductionListColumnComment from "./production-list-columns.comment.component";
|
|
||||||
import ProductionListDate from "./production-list-columns.date.component";
|
import ProductionListDate from "./production-list-columns.date.component";
|
||||||
import ProductionListColumnDetailPriority from "./production-list-columns.detailpriority.component";
|
import ProductionListColumnDetailPriority from "./production-list-columns.detailpriority.component";
|
||||||
import ProductionListEmployeeAssignment from "./production-list-columns.empassignment.component";
|
import ProductionListEmployeeAssignment from "./production-list-columns.empassignment.component";
|
||||||
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
|
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
|
||||||
import ProductionListColumnPaintPriority from "./production-list-columns.paintpriority.component";
|
import ProductionListColumnPaintPriority from "./production-list-columns.paintpriority.component";
|
||||||
import ProductionListColumnPartsReceived from "./production-list-columns.partsreceived.component";
|
|
||||||
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
|
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
|
||||||
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
|
||||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||||
|
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
||||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||||
|
import ProductionListColumnComment from "./production-list-columns.comment.component";
|
||||||
|
import ProductionListColumnPartsReceived from "./production-list-columns.partsreceived.component";
|
||||||
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
||||||
return [
|
return [
|
||||||
@@ -106,16 +104,6 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
<ProductionListDate record={record} field="actual_in" time />
|
<ProductionListDate record={record} field="actual_in" time />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: i18n.t("jobs.fields.actual_in") + " (HH:MM)",
|
|
||||||
dataIndex: "actual_in_time",
|
|
||||||
key: "actual_in_time",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
render: (text, record) => (
|
|
||||||
<TimeFormatter>{record.actual_in}</TimeFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: i18n.t("jobs.fields.scheduled_completion"),
|
title: i18n.t("jobs.fields.scheduled_completion"),
|
||||||
dataIndex: "scheduled_completion",
|
dataIndex: "scheduled_completion",
|
||||||
@@ -135,16 +123,6 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)",
|
|
||||||
dataIndex: "scheduled_completion_time",
|
|
||||||
key: "scheduled_completion_time",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
render: (text, record) => (
|
|
||||||
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: i18n.t("jobs.fields.date_last_contacted"),
|
title: i18n.t("jobs.fields.date_last_contacted"),
|
||||||
dataIndex: "date_last_contacted",
|
dataIndex: "date_last_contacted",
|
||||||
@@ -197,16 +175,6 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)",
|
|
||||||
dataIndex: "scheduled_delivery_time",
|
|
||||||
key: "scheduled_delivery_time",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
render: (text, record) => (
|
|
||||||
<TimeFormatter>{record.scheduled_delivery}</TimeFormatter>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: i18n.t("jobs.fields.ins_co_nm"),
|
title: i18n.t("jobs.fields.ins_co_nm"),
|
||||||
dataIndex: "ins_co_nm",
|
dataIndex: "ins_co_nm",
|
||||||
@@ -522,14 +490,6 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
|
|||||||
<ProductionListColumnPartsReceived record={record} />
|
<ProductionListColumnPartsReceived record={record} />
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: i18n.t("jobs.fields.partsstatus"),
|
|
||||||
dataIndex: "partsstatus",
|
|
||||||
key: "partsstatus",
|
|
||||||
render: (text, record) => (
|
|
||||||
<JobPartsQueueCount parts={record.joblines_status} record={record} />
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
export default r;
|
export default r;
|
||||||
|
|||||||
@@ -50,45 +50,50 @@ export default function ProductionListDate({
|
|||||||
"production-completion-soon"));
|
"production-completion-soon"));
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<div>
|
||||||
//trigger={["click"]}
|
<Dropdown
|
||||||
visible={visible}
|
//trigger={["click"]}
|
||||||
style={{
|
visible={visible}
|
||||||
height: "19px",
|
|
||||||
}}
|
|
||||||
overlay={
|
|
||||||
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
|
|
||||||
<FormDatePicker
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
value={(record[field] && moment(record[field])) || null}
|
|
||||||
onChange={handleChange}
|
|
||||||
format="MM/DD/YYYY"
|
|
||||||
isDateOnly={!time}
|
|
||||||
/>
|
|
||||||
{time && (
|
|
||||||
<TimePicker
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
value={(record[field] && moment(record[field])) || null}
|
|
||||||
onChange={handleChange}
|
|
||||||
minuteStep={15}
|
|
||||||
format="hh:mm a"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button onClick={() => setVisible(false)}>
|
|
||||||
{t("general.actions.close")}
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
onClick={() => setVisible(true)}
|
|
||||||
style={{
|
style={{
|
||||||
height: "19px",
|
height: "19px",
|
||||||
}}
|
}}
|
||||||
className={className}
|
overlay={
|
||||||
|
<Card
|
||||||
|
style={{ padding: "1rem" }}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<FormDatePicker
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
value={(record[field] && moment(record[field])) || null}
|
||||||
|
onChange={handleChange}
|
||||||
|
format="MM/DD/YYYY"
|
||||||
|
isDateOnly={!time}
|
||||||
|
/>
|
||||||
|
{time && (
|
||||||
|
<TimePicker
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
value={(record[field] && moment(record[field])) || null}
|
||||||
|
onChange={handleChange}
|
||||||
|
minuteStep={15}
|
||||||
|
format="hh:mm a"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button onClick={() => setVisible(false)}>
|
||||||
|
{t("general.actions.close")}
|
||||||
|
</Button>
|
||||||
|
</Card>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<DateFormatter bordered={false}>{record[field]}</DateFormatter>
|
<div
|
||||||
</div>
|
onClick={() => setVisible(true)}
|
||||||
</Dropdown>
|
style={{
|
||||||
|
height: "19px",
|
||||||
|
}}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<DateFormatter bordered={false}>{record[field]}</DateFormatter>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,11 +21,9 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import ScoreboardAddButton from "../job-scoreboard-add-button/job-scoreboard-add-button.component";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
//currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPrintCenterContext: (context) =>
|
setPrintCenterContext: (context) =>
|
||||||
@@ -36,11 +34,7 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ProductionListDetail);
|
)(ProductionListDetail);
|
||||||
|
|
||||||
export function ProductionListDetail({
|
export function ProductionListDetail({ jobs, setPrintCenterContext }) {
|
||||||
bodyshop,
|
|
||||||
jobs,
|
|
||||||
setPrintCenterContext,
|
|
||||||
}) {
|
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { selected } = search;
|
const { selected } = search;
|
||||||
@@ -65,7 +59,7 @@ export function ProductionListDetail({
|
|||||||
<PageHeader
|
<PageHeader
|
||||||
title={theJob.ro_number}
|
title={theJob.ro_number}
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space>
|
||||||
<ProductionRemoveButton jobId={theJob.id} />{" "}
|
<ProductionRemoveButton jobId={theJob.id} />{" "}
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -82,7 +76,6 @@ export function ProductionListDetail({
|
|||||||
<PrinterFilled />
|
<PrinterFilled />
|
||||||
{t("jobs.actions.printCenter")}
|
{t("jobs.actions.printCenter")}
|
||||||
</Button>
|
</Button>
|
||||||
<ScoreboardAddButton job={data ? data.jobs_by_pk : {}} />
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -149,12 +142,11 @@ export function ProductionListDetail({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
data={data ? data.jobs_by_pk : null}
|
data={data ? data.jobs_by_pk : null}
|
||||||
/>
|
/>
|
||||||
{!bodyshop.uselocalmediaserver && (
|
|
||||||
<JobDetailCardsDocumentsComponent
|
<JobDetailCardsDocumentsComponent
|
||||||
loading={loading}
|
loading={loading}
|
||||||
data={data ? data.jobs_by_pk : null}
|
data={data ? data.jobs_by_pk : null}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const ret = {
|
|||||||
"jobs:detail": 1,
|
"jobs:detail": 1,
|
||||||
"jobs:partsqueue": 4,
|
"jobs:partsqueue": 4,
|
||||||
"jobs:checklist-view": 2,
|
"jobs:checklist-view": 2,
|
||||||
"jobs:list-ready": 1,
|
|
||||||
"bills:enter": 2,
|
"bills:enter": 2,
|
||||||
"bills:view": 2,
|
"bills:view": 2,
|
||||||
"bills:list": 2,
|
"bills:list": 2,
|
||||||
@@ -66,8 +66,5 @@ const ret = {
|
|||||||
"timetickets:shiftedit": 5,
|
"timetickets:shiftedit": 5,
|
||||||
|
|
||||||
"users:editaccess": 4,
|
"users:editaccess": 4,
|
||||||
|
|
||||||
"inventory:list": 1,
|
|
||||||
"inventory:delete": 2,
|
|
||||||
};
|
};
|
||||||
export default ret;
|
export default ret;
|
||||||
|
|||||||
@@ -8,11 +8,8 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useApolloClient, useQuery } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import {
|
import { GET_BLOCKED_DAYS } from "../../graphql/scoreboard.queries";
|
||||||
GET_BLOCKED_DAYS,
|
|
||||||
QUERY_SCOREBOARD,
|
|
||||||
} from "../../graphql/scoreboard.queries";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -25,15 +22,10 @@ export default connect(
|
|||||||
mapDispatchToProps
|
mapDispatchToProps
|
||||||
)(ScoreboardDisplayComponent);
|
)(ScoreboardDisplayComponent);
|
||||||
|
|
||||||
export function ScoreboardDisplayComponent({ bodyshop }) {
|
export function ScoreboardDisplayComponent({
|
||||||
const scoreboardSubscription = useQuery(QUERY_SCOREBOARD, {
|
bodyshop,
|
||||||
variables: {
|
scoreboardSubscription,
|
||||||
start: moment().startOf("month"),
|
}) {
|
||||||
end: moment().endOf("month"),
|
|
||||||
},
|
|
||||||
pollInterval: 60000,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { data } = scoreboardSubscription;
|
const { data } = scoreboardSubscription;
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const scoreBoardlist = (data && data.scoreboard) || [];
|
const scoreBoardlist = (data && data.scoreboard) || [];
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export default function ScoreboardEntryEdit({ entry }) {
|
|||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("scoreboard.successes.updated"),
|
message: t("scoredboard.successes.updated"),
|
||||||
});
|
});
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,13 @@
|
|||||||
import React, { useState } from "react";
|
import React from "react";
|
||||||
import { Dropdown, Button, Table, Space, Card, Input } from "antd";
|
import { Dropdown, Button, Table, Space } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
|
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
||||||
|
|
||||||
export default function ScoreboardJobsList({ scoreBoardlist }) {
|
export default function ScoreboardJobsList({ scoreBoardlist }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchText, setSearchText] = useState("");
|
|
||||||
|
|
||||||
const jobs = scoreBoardlist
|
|
||||||
? searchText === ""
|
|
||||||
? scoreBoardlist
|
|
||||||
: scoreBoardlist.filter(
|
|
||||||
(sb) =>
|
|
||||||
(sb.job.ro_number || "")
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(sb.job.ownr_co_nm || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(sb.job.ownr_fn || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(sb.job.ownr_ln || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(sb.job.v_model_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase()) ||
|
|
||||||
(sb.job.v_make_desc || "")
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchText.toLowerCase())
|
|
||||||
)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@@ -49,25 +20,7 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
|||||||
</Link>
|
</Link>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: t("jobs.fields.owner"),
|
|
||||||
dataIndex: "owner",
|
|
||||||
key: "owner",
|
|
||||||
ellipsis: true,
|
|
||||||
|
|
||||||
render: (text, record) => <OwnerNameDisplay ownerObject={record.job} />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("jobs.fields.vehicle"),
|
|
||||||
dataIndex: "vehicle",
|
|
||||||
key: "vehicle",
|
|
||||||
ellipsis: true,
|
|
||||||
render: (text, record) => (
|
|
||||||
<span>{`${record.job.v_model_yr || ""} ${
|
|
||||||
record.job.v_make_desc || ""
|
|
||||||
} ${record.job.v_model_desc || ""}`}</span>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("scoreboard.fields.date"),
|
title: t("scoreboard.fields.date"),
|
||||||
dataIndex: "date",
|
dataIndex: "date",
|
||||||
@@ -98,29 +51,17 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const overlay = (
|
const overlay = (
|
||||||
<Card
|
<div style={{ width: "50vw", padding: "1rem" }}>
|
||||||
style={{ maxWidth: "90vw", padding: "1rem" }}
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
extra={
|
|
||||||
<Input.Search
|
|
||||||
placeholder={t("general.labels.search")}
|
|
||||||
onChange={(e) => {
|
|
||||||
setSearchText(e.target.value);
|
|
||||||
}}
|
|
||||||
value={searchText}
|
|
||||||
enterButton
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Table
|
<Table
|
||||||
pagination={false}
|
pagination={false}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
dataSource={jobs}
|
dataSource={scoreBoardlist}
|
||||||
|
scroll={{ x: true, y: "15rem" }}
|
||||||
|
style={{ padding: "1rem" }}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
|
|
||||||
const values = useMemo(() => {
|
const values = useMemo(() => {
|
||||||
const dateHash = _.groupBy(scoreBoardlist, "date");
|
const dateHash = _.groupBy(scoreBoardlist, "date");
|
||||||
|
console.log(
|
||||||
|
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 31 ~ values ~ dateHash",
|
||||||
|
dateHash
|
||||||
|
);
|
||||||
|
|
||||||
let ret = {
|
let ret = {
|
||||||
todayBody: 0,
|
todayBody: 0,
|
||||||
@@ -67,6 +71,10 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}, [scoreBoardlist]);
|
}, [scoreBoardlist]);
|
||||||
|
console.log(
|
||||||
|
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 51 ~ values ~ values",
|
||||||
|
values
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -177,27 +185,6 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
|
|||||||
<Statistic value={values.toDatePaint.toFixed(1)} />
|
<Statistic value={values.toDatePaint.toFixed(1)} />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
|
||||||
<Col {...statSpans}></Col>
|
|
||||||
<Col {...statSpans}>
|
|
||||||
<Statistic
|
|
||||||
value={(values.todayPaint + values.todayBody).toFixed(1)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col {...statSpans}></Col>
|
|
||||||
<Col {...statSpans}>
|
|
||||||
<Statistic
|
|
||||||
value={(values.weeklyPaint + values.weeklyBody).toFixed(1)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col {...statSpans}></Col>
|
|
||||||
<Col {...statSpans}></Col>
|
|
||||||
<Col {...statSpans}>
|
|
||||||
<Statistic
|
|
||||||
value={(values.toDatePaint + values.toDateBody).toFixed(1)}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user