Compare commits

..

2 Commits

Author SHA1 Message Date
Patrick Fic
3f070af7ab Merge branch 'release/2022-07-15' of bitbucket.org:snaptsoft/bodyshop into release/2022-07-15 2022-07-20 14:16:43 -07:00
Patrick Fic
bc4b6fa007 IO-1988 Download LMS as zip. 2022-07-20 14:16:41 -07:00
171 changed files with 18793 additions and 72690 deletions

View File

@@ -1,180 +0,0 @@
version: 2.1
orbs:
#snyk: snyk/snyk@0.0.8
#cypress: cypress-io/cypress@1.23.0
aws-s3: circleci/aws-s3@2.0.0
eb: circleci/aws-elastic-beanstalk@1.0.2
jira: circleci/jira@1.3.1
jobs:
api-deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
- jira/notify
hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_PROD_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build
- aws-s3/sync:
from: build
to: "s3://imex-online-production/"
- jira/notify
test-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
test-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build:test
- aws-s3/sync:
from: build
to: "s3://imex-online-test/"
- jira/notify
admin-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/admin
steps:
- checkout:
path: ~/repo
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: npm i
- save_cache:
paths:
- node_modules
- ~/.npm
- ~/.cache
key: v1-dependencies-{{ checksum "package.json" }}
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://adm.imex.online/"
workflows:
deploy_and_build:
jobs:
- api-deploy:
filters:
branches:
only: master
- app-build:
filters:
branches:
only: master
- hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: master
- test-app-build:
filters:
branches:
only: test
- test-hasura-migrate:
secret: ${HASURA_TEST_SECRET}
filters:
branches:
only: test
#- admin-app-build:
#filters:
#branches:
#only: master

View File

@@ -16,6 +16,6 @@
"rules": {
"no-console": "off"
},
"settings": {}
//"plugins": ["cypress"]
"settings": {},
"plugins": ["cypress"]
}

5
.gitignore vendored
View File

@@ -113,8 +113,3 @@ firebase/.env
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
logs/oAuthClient-log.log
.node-persist/**
/*.env.*

View File

@@ -19,6 +19,3 @@ npx deadfile ./src/index.js --exclude build templates
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
hasura migrate apply --version "1620771761757" --skip-execution --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
Generate the license file:
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +0,0 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -1,13 +0,0 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk

View File

@@ -1,14 +0,0 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -4,18 +4,18 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.6.9",
"@apollo/client": "^3.6.6",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.5",
"@craco/craco": "^6.4.3",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.7.0",
"@sentry/tracing": "^7.7.0",
"@splitsoftware/splitio-react": "^1.6.0",
"@stripe/react-stripe-js": "^1.9.0",
"@stripe/stripe-js": "^1.32.0",
"@tanem/react-nprogress": "^5.0.8",
"antd": "^4.22.3",
"@sentry/react": "^7.1.1",
"@sentry/tracing": "^7.1.1",
"@splitsoftware/splitio-react": "^1.4.1",
"@stripe/react-stripe-js": "^1.8.1",
"@stripe/stripe-js": "^1.31.0",
"@tanem/react-nprogress": "^5.0.1",
"antd": "^4.21.0",
"apollo-link-logger": "^2.0.0",
"axios": "^0.27.2",
"craco-less": "^1.20.0",
@@ -24,33 +24,33 @@
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.9.1",
"firebase": "^9.8.2",
"graphql": "^16.5.0",
"i18next": "^21.8.14",
"i18next": "^21.8.9",
"i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.9.0",
"jsoneditor": "^9.8.0",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.9",
"logrocket": "^3.0.1",
"markerjs2": "^2.22.0",
"libphonenumber-js": "^1.10.6",
"logrocket": "^3.0.0",
"markerjs2": "^2.21.4",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"normalize-url": "^7.0.3",
"phone": "^3.1.23",
"phone": "^3.1.20",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^1.5.0",
"react-big-calendar": "^0.40.1",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.2.1",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.18.1",
"react-i18next": "^11.17.0",
"react-icons": "^4.4.0",
"react-number-format": "^4.9.3",
"react-redux": "^7.2.8",
@@ -60,13 +60,13 @@
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.12",
"recharts": "^2.1.10",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.4",
"redux-state-sync": "^3.1.2",
"reselect": "^4.1.6",
"sass": "^1.54.0",
"sass": "^1.51.0",
"socket.io-client": "^4.5.1",
"styled-components": "^5.3.5",
"subscriptions-transport-ws": "^0.11.0",
@@ -119,9 +119,9 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.19.0",
"@testing-library/cypress": "^8.0.3",
"cypress": "^10.3.1",
"@sentry/webpack-plugin": "^1.18.9",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.6.1",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { useClient } from "@splitsoftware/splitio-react";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Result } from "antd";
import LogRocket from "logrocket";
import React, { lazy, Suspense, useEffect } from "react";
@@ -51,7 +51,11 @@ export function App({
online,
setOnline,
}) {
const client = useClient();
const { LogRocket_Tracking } = useTreatments(
["LogRocket_Tracking"],
{},
bodyshop && bodyshop.imexshopid
);
useEffect(() => {
if (!navigator.onLine) {
@@ -74,15 +78,15 @@ export function App({
setOnline(true);
});
useEffect(() => {
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
if (client.getTreatment("LogRocket_Tracking") === "on") {
console.log("LR Start");
if (currentUser.authorized) {
if (
process.env.NODE_ENV === "production" &&
LogRocket_Tracking.treatment === "on"
) {
LogRocket.init("gvfvfw/bodyshopapp");
}
}
}, [bodyshop, client, currentUser.authorized]);
}, [currentUser.authorized, LogRocket_Tracking.treatment]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")} />;

View File

@@ -142,9 +142,3 @@
}
}
}
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important;
}

View File

@@ -1,20 +1,19 @@
import { Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
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({
bodyshop: selectBodyshop,
@@ -185,13 +184,6 @@ export function AccountingPayablesTableComponent({
<Card
extra={
<Space wrap>
<PaymentMarkSelectedExported
paymentIds={selectedPayments}
disabled={transInProgress || selectedPayments.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedPayments}
refetch={refetch}
/>
<PaymentsExportAllButton
paymentIds={selectedPayments}
disabled={transInProgress || selectedPayments.length === 0}

View File

@@ -4,8 +4,6 @@ import { useQuery } from "@apollo/client";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import AlertComponent from "../alert/alert.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import EmailAuditTrailListComponent from "./email-audit-trail-list.component";
import { Card, Row } from "antd";
export default function AuditTrailListContainer({ recordId }) {
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
@@ -20,20 +18,10 @@ export default function AuditTrailListContainer({ recordId }) {
{error ? (
<AlertComponent type="error" message={error.message} />
) : (
<Row gutter={[16, 16]}>
<Card>
<AuditTrailListComponent
loading={loading}
data={data ? data.audit_trail : []}
/>
</Card>
<Card>
<EmailAuditTrailListComponent
loading={loading}
data={data ? data.audit_trail : []}
/>
</Card>
</Row>
<AuditTrailListComponent
loading={loading}
data={data ? data.audit_trail : null}
/>
)}
</div>
);

View File

@@ -1,63 +0,0 @@
import { Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
export default function EmailAuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
});
const { t } = useTranslation();
const columns = [
{
title: t("audit.fields.created"),
dataIndex: " created",
key: " created",
width: "10%",
render: (text, record) => (
<DateTimeFormatter>{record.created}</DateTimeFormatter>
),
sorter: (a, b) => a.created - b.created,
sortOrder:
state.sortedInfo.columnKey === "created" && state.sortedInfo.order,
},
{
title: t("audit.fields.useremail"),
dataIndex: "useremail",
key: "useremail",
width: "10%",
sorter: (a, b) => alphaSort(a.useremail, b.useremail),
sortOrder:
state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order,
},
];
const formItemLayout = {
labelCol: {
xs: { span: 12 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
return (
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey="id"
dataSource={data}
onChange={handleTableChange}
/>
);
}

View File

@@ -1,251 +0,0 @@
import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE
} from "../../graphql/bill-lines.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 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 JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillDetailEditReturn from "./bill-detail-edit-return.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
}) {
const search = queryString.parse(useLocation().search);
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleSave = () => {
//It's got a previously deducted bill line!
if (
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
else {
form.submit();
}
};
const handleFinish = async (values) => {
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
billlines.forEach((l) => {
delete l.selected;
});
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
updates.push(
updateBillLine({
variables: {
billLineId: il.id,
billLine: {
...il,
deductedfromlbr: deductedfromlbr,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
} else {
//It's a new line, have to insert it.
updates.push(
insertBillLine({
variables: {
billLines: [
{
...il,
deductedfromlbr: deductedfromlbr,
billid: search.billid,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
],
},
})
);
}
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setUpdateLoading(false);
};
if (error) return <AlertComponent message={error.message} type="error" />;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<>
{loading && <LoadingSkeleton />}
{data && (
<>
<PageHeader
title={
data &&
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
}
extra={
<Space>
<BillDetailEditReturn data={data} />
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
title={t("bills.labels.editadjwarning")}
>
<Button
htmlType="submit"
disabled={exported}
onClick={handleSave}
loading={updateLoading}
type="primary"
>
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>
<Form
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }}
invoice_number={data ? data.bills_by_pk.invoice_number : null}
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>
</>
)}
</>
);
}
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -1,185 +0,0 @@
import { Button, Checkbox, Form, Modal } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditReturn);
export function BillDetailEditReturn({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
data,
disabled,
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const handleFinish = ({ billlines }) => {
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
setPartsOrderContext({
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,
linesToOrder: data.bills_by_pk.billlines
.filter((l) => selectedLines.includes(l.id))
.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
delete search.billid;
history.push({ search: queryString.stringify(search) });
setVisible(false);
};
useEffect(() => {
if (visible === false) form.resetFields();
}, [visible, form]);
return (
<>
<Modal
visible={visible}
onCancel={() => setVisible(false)}
destroyOnClose
title={t("bills.actions.return")}
onOk={() => form.submit()}
>
<Form
initialValues={data && data.bills_by_pk}
onFinish={handleFinish}
form={form}
>
<Form.List name={["billlines"]}>
{(fields, { add, remove, move }) => {
return (
<table style={{ tableLayout: "auto", width: "100%" }}>
<thead>
<tr>
<td>
<Checkbox
onChange={(e) => {
form.setFieldsValue({
billlines: form
.getFieldsValue()
.billlines.map((b) => ({
...b,
selected: e.target.checked,
})),
});
}}
/>
</td>
<td>{t("billlines.fields.line_desc")}</td>
<td>{t("billlines.fields.quantity")}</td>
<td>{t("billlines.fields.actual_price")}</td>
<td>{t("billlines.fields.actual_cost")}</td>
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.key}>
<td>
<Form.Item
// label={t("joblines.fields.selected")}
key={`${index}selected`}
name={[field.name, "selected"]}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
</td>
<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
// label={t("joblines.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.actual_price")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.actual_cost")}
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
</tr>
))}
</tbody>
</table>
);
}}
</Form.List>
</Form>
</Modal>
<Button
disabled={data.bills_by_pk.is_credit_memo || disabled}
onClick={() => {
setVisible(true);
}}
>
{t("bills.actions.return")}
</Button>
</>
);
}

View File

@@ -1,12 +1,68 @@
import { Drawer, Grid } from "antd";
import { useMutation, useQuery } from "@apollo/client";
import {
Button,
Drawer,
Form,
Grid,
PageHeader,
Popconfirm,
Space,
} from "antd";
import moment from "moment";
import queryString from "query-string";
import React from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import BillDetailEditComponent from "./bill-detail-edit-component";
import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.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 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 JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
export default function BillDetailEditcontainer() {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
@@ -24,6 +80,114 @@ export default function BillDetailEditcontainer() {
? bpoints[selectedBreakpoint[0]]
: "100%";
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleSave = () => {
//It's got a previously deducted bill line!
if (
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
else {
form.submit();
}
};
const handleFinish = async (values) => {
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
updates.push(
updateBillLine({
variables: {
billLineId: il.id,
billLine: {
...il,
deductedfromlbr: deductedfromlbr,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
} else {
//It's a new line, have to insert it.
updates.push(
insertBillLine({
variables: {
billLines: [
{
...il,
deductedfromlbr: deductedfromlbr,
billid: search.billid,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
],
},
})
);
}
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setUpdateLoading(false);
};
useEffect(() => {
if (search.billid && data) {
form.resetFields();
}
}, [form, search.billid, data]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<Drawer
width={drawerPercentage}
@@ -31,10 +195,119 @@ export default function BillDetailEditcontainer() {
delete search.billid;
history.push({ search: queryString.stringify(search) });
}}
destroyOnClose
visible={search.billid}
>
<BillDetailEditComponent />
{loading && <LoadingSkeleton />}
{!loading && (
<>
<PageHeader
title={
data &&
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
}
extra={
<Space>
<Button
disabled={data.bills_by_pk.is_credit_memo}
onClick={() => {
delete search.billid;
history.push({ search: queryString.stringify(search) });
setPartsOrderContext({
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,
linesToOrder: data.bills_by_pk.billlines.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
}}
>
{t("bills.actions.return")}
</Button>
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
title={t("bills.labels.editadjwarning")}
>
<Button
htmlType="submit"
disabled={exported}
onClick={handleSave}
loading={updateLoading}
type="primary"
>
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>
<Form
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }}
invoice_number={data ? data.bills_by_pk.invoice_number : null}
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>
</>
)}
</Drawer>
);
}
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -1,5 +1,5 @@
import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
import { Button, Form, Modal, notification, Space } from "antd";
import _ from "lodash";
import React, { useEffect, useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
@@ -26,9 +26,6 @@ import BillFormContainer from "../bill-form/bill-form.container";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility";
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
import useLocalStorage from "../../utils/useLocalStorage";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
billEnterModal: selectBillEnterModal,
@@ -41,8 +38,6 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(insertAuditTrail({ jobid, operation })),
});
const Templates = TemplateList("job_special");
function BillEnterModalContainer({
billEnterModal,
toggleModalVisible,
@@ -59,10 +54,7 @@ function BillEnterModalContainer({
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
const [loading, setLoading] = useState(false);
const client = useApolloClient();
const [generateLabel, setGenerateLabel] = useLocalStorage(
"enter_bill_generate_label",
false
);
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
@@ -229,26 +221,23 @@ function BillEnterModalContainer({
});
}
}
//If it's not a credit memo, update the statuses.
if (!values.is_credit_memo) {
await Promise.all(
remainingValues.billlines
.filter((il) => il.joblineid !== "noline")
.map((li) => {
return updateJobLines({
variables: {
lineId: li.joblineid,
line: {
location: li.location || location,
status:
bodyshop.md_order_statuses.default_received || "Received*",
},
await Promise.all(
remainingValues.billlines
.filter((il) => il.joblineid !== "noline")
.map((li) => {
return updateJobLines({
variables: {
lineId: li.joblineid,
line: {
location: li.location || location,
status:
bodyshop.md_order_statuses.default_received || "Received*",
},
});
})
);
}
},
});
})
);
/////////////////////////
if (upload && upload.length > 0) {
@@ -286,20 +275,6 @@ function BillEnterModalContainer({
notification["success"]({
message: t("bills.successes.created"),
});
if (generateLabel) {
GenerateDocument(
{
name: Templates.parts_invoice_label_single.key,
variables: {
id: billId,
},
},
{},
"p"
);
}
if (billEnterModal.actions.refetch) billEnterModal.actions.refetch();
insertAuditTrail({
@@ -355,12 +330,6 @@ function BillEnterModalContainer({
}}
footer={
<Space>
<Checkbox
checked={generateLabel}
onChange={(e) => setGenerateLabel(e.target.checked)}
>
{t("bills.labels.generatepartslabel")}
</Checkbox>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
<Button loading={loading} onClick={() => form.submit()}>
{t("general.actions.save")}

View File

@@ -1,14 +1,14 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button, Form,
Button,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip
Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -17,8 +17,9 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.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({
//currentUser: selectCurrentUser
@@ -171,7 +172,7 @@ export function BillEnterModalLinesComponent({
};
},
formInput: (record, index) => (
<InputNumber precision={0} min={1} disabled={disabled} />
<InputNumber precision={0} min={0} disabled={disabled} />
),
},
{

View File

@@ -27,10 +27,6 @@ const BillLineSearchSelect = (
option.oem_partno
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.alt_partno &&
option.alt_partno
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.act_price &&
option.act_price.toString().startsWith(inputValue.toString()))
);
@@ -52,17 +48,14 @@ const BillLineSearchSelect = (
line_desc={item.line_desc}
part_qty={item.part_qty}
oem_partno={item.oem_partno}
alt_partno={item.alt_partno}
act_price={item.act_price}
style={{
...(item.removed ? { textDecoration: "line-through" } : {}),
}}
>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
</span>
<span>{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}`}</span>
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`

View File

@@ -12,7 +12,6 @@ import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
@@ -59,15 +58,39 @@ export function BillsListTableComponent({
</Button>
)}
<BillDeleteButton bill={record} />
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }}
<Button
disabled={
record.is_credit_memo ||
record.vendorid === bodyshop.inhousevendorid ||
jobRO
}
/>
onClick={() => {
setPartsOrderContext({
actions: {},
context: {
jobId: job.id,
vendorId: record.vendorid,
returnFromBill: record.id,
invoiceNumber: record.invoice_number,
linesToOrder: record.billlines.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
}}
>
{t("bills.actions.return")}
</Button>
{record.isinhouse && (
<PrintWrapperComponent
templateObject={{

View File

@@ -39,13 +39,7 @@ export function ContractsFindModalContainer({
if (!claim || !shortclaim) return;
const trimmedShortClaim = shortclaim.trim();
// const trimmedClaim = claim.trim();
if (amount.slice(-1) === "-") {
}
claimNumbers.push({
claim: trimmedShortClaim,
amount: amount.slice(-1) === "-" ? parseFloat(amount) * -1 : amount,
});
claimNumbers.push({ claim: trimmedShortClaim, amount });
});
await GenerateDocument(

View File

@@ -9,7 +9,6 @@ import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
import JobDocumentsLocalGalleryExternal from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({
@@ -59,24 +58,17 @@ export function ChatMediaSelector({
{selectedMedia.filter((s) => s.isSelected).length >= 10 ? (
<div style={{ color: "red" }}>{t("messaging.labels.maxtenimages")}</div>
) : null}
{!bodyshop.uselocalmediaserver && data && (
{data && (
<JobDocumentsGalleryExternal
data={data ? data.documents : []}
externalMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
{bodyshop.uselocalmediaserver && visible && (
<JobDocumentsLocalGalleryExternal
externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={
conversation.job_conversations[0] &&
conversation.job_conversations[0].jobid
}
/>
)}
</div>
);
if (bodyshop.uselocalmediaserver) return null;
return (
<Popover
content={

View File

@@ -279,80 +279,31 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
<Form.Item label={t("courtesycars.fields.notes")} name="notes">
<Input.TextArea />
</Form.Item>
<div>
<Form.Item
label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) =>
p.registrationexpires !== c.registrationexpires
}
>
{() => {
const expires = form.getFieldValue("registrationexpires");
const dateover = expires && moment(expires).isBefore(moment());
if (dateover)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.dateinpast")}
</span>
</Space>
);
return <></>;
}}
</Form.Item>
</div>
<div>
<Form.Item
label={t("courtesycars.fields.insuranceexpires")}
name="insuranceexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
shouldUpdate={(p, c) =>
p.insuranceexpires !== c.insuranceexpires
}
>
{() => {
const expires = form.getFieldValue("insuranceexpires");
const dateover = expires && moment(expires).isBefore(moment());
if (dateover)
return (
<Space direction="vertical" style={{ color: "tomato" }}>
<span>
<WarningFilled style={{ marginRight: ".3rem" }} />
{t("contracts.labels.dateinpast")}
</span>
</Space>
);
return <></>;
}}
</Form.Item>
</div>
<Form.Item
label={t("courtesycars.fields.registrationexpires")}
name="registrationexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
label={t("courtesycars.fields.insuranceexpires")}
name="insuranceexpires"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item label={t("courtesycars.fields.dailycost")} name="dailycost">
<CurrencyInput />
</Form.Item>

View File

@@ -34,9 +34,6 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
<Option value="courtesycars.status.sold">
{t("courtesycars.status.sold")}
</Option>
<Option value="courtesycars.status.leasereturn">
{t("courtesycars.status.leasereturn")}
</Option>
</Select>
);
};

View File

@@ -52,10 +52,6 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.sold"),
value: "courtesycars.status.sold",
},
{
text: t("courtesycars.status.leasereturn"),
value: "courtesycars.status.leasereturn",
},
],
onFilter: (value, record) => value.includes(record.status),
sortOrder:

View File

@@ -1,152 +0,0 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Form, Input, Table } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(DmsAllocationsSummaryAp);
export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]);
useEffect(() => {
socket.on("ap-export-success", (billid) => {
setAllocationsSummary((allocationsSummary) =>
allocationsSummary.map((a) => {
if (a.billid !== billid) return a;
return { ...a, status: "Successful" };
})
);
});
socket.on("ap-export-failure", ({ billid, error }) => {
allocationsSummary.map((a) => {
if (a.billid !== billid) return a;
return { ...a, status: error };
});
});
if (socket.disconnected) socket.connect();
return () => {
socket.removeListener("ap-export-success");
socket.removeListener("ap-export-failure");
//socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (socket.connected) {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => {
setAllocationsSummary(ack);
socket.allocationsSummary = ack;
});
}
}, [socket, socket.connected, billids]);
console.log(allocationsSummary);
const columns = [
{
title: t("general.labels.status"),
dataIndex: "status",
key: "status",
},
{
title: t("jobs.fields.ro_number"),
dataIndex: ["Posting", "Reference"],
key: "reference",
},
{
title: t("jobs.fields.dms.lines"),
dataIndex: "Lines",
key: "Lines",
render: (text, record) => (
<table style={{ tableLayout: "auto", width: "100%" }}>
<tr>
<th>{t("bills.fields.invoice_number")}</th>
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
<th>{t("jobs.fields.dms.amount")}</th>
</tr>
{record.Posting.Lines.map((l, idx) => (
<tr key={idx}>
<td>{l.InvoiceNumber}</td>
<td>{l.Account}</td>
<td>{l.Amount}</td>
</tr>
))}
</table>
),
},
];
const handleFinish = async (values) => {
socket.emit(`pbs-export-ap`, {
billids,
txEnvelope: values,
});
};
return (
<Card
title={title}
extra={
<Button
onClick={() => {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) =>
setAllocationsSummary(ack)
);
}}
>
<SyncOutlined />
</Button>
}
>
<Table
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
dataSource={allocationsSummary}
locale={{ emptyText: t("dms.labels.refreshallocations") }}
/>
<Form layout="vertical" onFinish={handleFinish}>
<Form.Item
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.default_journal
}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Button
disabled={!allocationsSummary || allocationsSummary.length === 0}
htmlType="submit"
>
{t("jobs.actions.dms.post")}
</Button>
</Form>
</Card>
);
}

View File

@@ -1,4 +1,4 @@
import { Alert, Button, Card, Table, Typography } from "antd";
import { Button, Card, Table, Typography } from "antd";
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -90,9 +90,6 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
</Button>
}
>
{bodyshop.pbs_configuration?.disablebillwip && (
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
)}
<Table
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}

View File

@@ -23,7 +23,6 @@ export function DocumentsLocalUploadComponent({
vendorid,
invoice_number,
callbackAfterUpload,
allowAllTypes,
}) {
const [fileList, setFileList] = useState([]);
@@ -53,9 +52,7 @@ export function DocumentsLocalUploadComponent({
},
})
}
{...(!allowAllTypes && {
accept: "audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx",
})}
accept="audio/*, video/*, image/*, .pdf, .doc, .docx, .xls, .xlsx"
>
{children || (
<>

View File

@@ -2,8 +2,6 @@ import cleanAxios from "../../utils/CleanAxios";
import { store } from "../../redux/store";
import { addMediaForJob } from "../../redux/media/media.actions";
import normalizeUrl from "normalize-url";
import { notification } from "antd";
import i18n from "i18next";
export const handleUpload = async ({ ev, context }) => {
const { onError, onSuccess, onProgress, file } = ev;
@@ -47,11 +45,6 @@ export const handleUpload = async ({ ev, context }) => {
}
} else {
onSuccess && onSuccess(file);
notification.open({
type: "success",
key: "docuploadsuccess",
message: i18n.t("documents.successes.insert"),
});
store.dispatch(
addMediaForJob({
jobid,
@@ -60,11 +53,6 @@ export const handleUpload = async ({ ev, context }) => {
...d,
selected: false,
src: normalizeUrl(`${bodyshop.localmediaserverhttp}/${d.src}`),
...(d.optimized && {
optimized: normalizeUrl(
`${bodyshop.localmediaserverhttp}/${d.optimized}`
),
}),
thumbnail: normalizeUrl(
`${bodyshop.localmediaserverhttp}/${d.thumbnail}`
),

View File

@@ -5,15 +5,12 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { GET_DOCUMENTS_BY_JOB } from "../../graphql/documents.queries";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import JobDocumentsGalleryExternal from "../jobs-documents-gallery/jobs-documents-gallery.external.component";
import JobsDocumentsLocalGalleryExternalComponent from "../jobs-documents-local-gallery/jobs-documents-local-gallery.external.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
emailConfig: selectEmailConfig,
});
const mapDispatchToProps = (dispatch) => ({
@@ -28,7 +25,6 @@ export function EmailDocumentsComponent({
emailConfig,
form,
selectedMediaState,
bodyshop,
}) {
const { t } = useTranslation();
@@ -56,18 +52,12 @@ export function EmailDocumentsComponent({
10485760 - new Blob([form.getFieldValue("html")]).size ? (
<div style={{ color: "red" }}>{t("general.errors.sizelimit")}</div>
) : null}
{!bodyshop.uselocalmediaserver && data && (
{data && (
<JobDocumentsGalleryExternal
data={data ? data.documents : []}
externalMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
{bodyshop.uselocalmediaserver && (
<JobsDocumentsLocalGalleryExternalComponent
externalMediaState={[selectedMedia, setSelectedMedia]}
jobId={emailConfig.jobid}
/>
)}
</div>
);
}

View File

@@ -160,13 +160,14 @@ export function EmailOverlayComponent({
</Form.Item>
<Tabs>
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
<EmailDocumentsComponent
selectedMediaState={selectedMediaState}
form={form}
/>
</Tabs.TabPane>
{!bodyshop.uselocalmediaserver && (
<Tabs.TabPane tab={t("emails.labels.documents")} key="documents">
<EmailDocumentsComponent
selectedMediaState={selectedMediaState}
form={form}
/>
</Tabs.TabPane>
)}
<Tabs.TabPane tab={t("emails.labels.attachments")} key="attachments">
{bodyshop.uselocalmediaserver && emailConfig.jobid && (
<a href={CreateExplorerLinkForJob({ jobid: emailConfig.jobid })}>

View File

@@ -1,4 +1,4 @@
import { Button, Divider, Form, Modal, notification, Space } from "antd";
import { Divider, Form, Modal, notification } from "antd";
import axios from "axios";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -77,9 +77,6 @@ export function EmailOverlayContainer({
setSending(true);
try {
await axios.post("/sendemail", {
bodyshopid: bodyshop.id,
jobid: emailConfig.jobid,
...defaultEmailFrom,
ReplyTo: {
Email: from,
@@ -175,77 +172,36 @@ export function EmailOverlayContainer({
<Modal
destroyOnClose={true}
visible={modalVisible}
maskClosable={false}
width={"80%"}
onOk={() => form.submit()}
title={t("emails.labels.emailpreview")}
onCancel={() => {
toggleEmailOverlayVisible();
}}
//closeIcon={() => null}
okText={t("general.actions.send")}
okButtonProps={{
loading: sending,
disabled:
selectedMedia &&
(selectedMedia
( (selectedMedia
.filter((s) => s.isSelected)
.reduce((acc, val) => (acc = acc + val.size), 0) >=
10485760 - new Blob([form.getFieldValue("html")]).size ||
selectedMedia.filter((s) => s.isSelected).length > 10),
10485760 - new Blob([form.getFieldValue("html")]).size) || selectedMedia.filter((s) => s.isSelected).length > 10),
}}
>
<div>
<div
style={{
// marginTop: "3rem",
display: "flex",
justifyContent: "flex-end",
}}
>
<Space style={{ alignSelf: "flex-end" }} align="right">
<Button
onClick={() => {
toggleEmailOverlayVisible();
}}
>
{t("general.actions.cancel")}
</Button>
<Button
loading={sending}
onClick={() => form.submit()}
disabled={
selectedMedia &&
(selectedMedia
.filter((s) => s.isSelected)
.reduce((acc, val) => (acc = acc + val.size), 0) >=
10485760 - new Blob([form.getFieldValue("html")]).size ||
selectedMedia.filter((s) => s.isSelected).length > 10)
}
type="primary"
>
{t("general.actions.send")}
</Button>
</Space>
</div>
<Form layout="vertical" form={form} onFinish={handleFinish}>
{loading && (
<div>
<LoadingSkeleton />
<Divider>{t("emails.labels.preview")}</Divider>
<LoadingSpinner message={t("emails.labels.generatingemail")} />
</div>
)}
{!loading && (
<EmailOverlayComponent
form={form}
selectedMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
</Form>
</div>
<Form layout="vertical" form={form} onFinish={handleFinish}>
{loading && (
<div>
<LoadingSkeleton />
<Divider>{t("emails.labels.preview")}</Divider>
<LoadingSpinner message={t("emails.labels.generatingemail")} />
</div>
)}
{!loading && (
<EmailOverlayComponent
form={form}
selectedMediaState={[selectedMedia, setSelectedMedia]}
/>
)}
</Form>
</Modal>
);
}

View File

@@ -5,7 +5,7 @@ import AlertComponent from "../alert/alert.component";
import { Prompt, useLocation } from "react-router-dom";
import "./form-fields-changed.styles.scss";
export default function FormsFieldChanged({ form, skipPrompt }) {
export default function FormsFieldChanged({ form }) {
const { t } = useTranslation();
const handleReset = () => {
@@ -25,7 +25,7 @@ export default function FormsFieldChanged({ form, skipPrompt }) {
return (
<Space direction="vertical" style={{ width: "100%" }}>
<Prompt
when={skipPrompt ? false : true}
when={true}
message={(location) => {
if (loc.pathname === location.pathname) return false;
return t("general.messages.unsavedchangespopup");

View File

@@ -12,7 +12,7 @@ import Icon, {
FileAddFilled,
FileAddOutlined,
FileFilled,
//GlobalOutlined,
GlobalOutlined,
HomeFilled,
ImportOutlined,
LineChartOutlined,
@@ -89,11 +89,6 @@ function Header({
{},
bodyshop && bodyshop.imexshopid
);
const { DmsAp } = useTreatments(
["DmsAp"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation();
@@ -269,11 +264,10 @@ function Header({
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item>
{(!(
{!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) ||
DmsAp.treatment === "on") && (
) && (
<Menu.Item key="payables">
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
@@ -374,27 +368,25 @@ function Header({
<Menu.Item key="profile">
<Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
</Menu.Item>
{
// <Menu.SubMenu
// key="langselecter"
// title={
// <span>
// <GlobalOutlined />
// <span>{t("menus.currentuser.languageselector")}</span>
// </span>
// }
// >
// <Menu.Item actiontype="lang-select" key="en-US">
// {t("general.languages.english")}
// </Menu.Item>
// <Menu.Item actiontype="lang-select" key="fr-CA">
// {t("general.languages.french")}
// </Menu.Item>
// <Menu.Item actiontype="lang-select" key="es-MX">
// {t("general.languages.spanish")}
// </Menu.Item>
// </Menu.SubMenu>
}
<Menu.SubMenu
key="langselecter"
title={
<span>
<GlobalOutlined />
<span>{t("menus.currentuser.languageselector")}</span>
</span>
}
>
<Menu.Item actiontype="lang-select" key="en-US">
{t("general.languages.english")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="fr-CA">
{t("general.languages.french")}
</Menu.Item>
<Menu.Item actiontype="lang-select" key="es-MX">
{t("general.languages.spanish")}
</Menu.Item>
</Menu.SubMenu>
</Menu.SubMenu>
<Menu.SubMenu key="recent" title={<ClockCircleFilled />}>
{recentItems.map((i, idx) => (

View File

@@ -19,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(Jobd3RdPartyModal);
export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
export function Jobd3RdPartyModal({ bodyshop, jobId }) {
const [isModalVisible, setIsModalVisible] = useState(false);
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -33,11 +33,6 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
);
const showModal = () => {
form.setFieldsValue({
ded_amt: job.ded_amt,
depreciation: job.depreciation_taxes,
custgst: job.ca_customer_gst,
});
setIsModalVisible(true);
};
@@ -47,7 +42,6 @@ export function Jobd3RdPartyModal({ bodyshop, jobId, job }) {
};
const handleCancel = () => {
form.resetFields();
setIsModalVisible(false);
};
const handleFinish = (values) => {

View File

@@ -1,24 +1,13 @@
import { useQuery } from "@apollo/client";
import { Button, Card, Col, Row, Table, Tag } from "antd";
import { SyncOutlined } from "@ant-design/icons";
import { Card, Table } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobAuditTrail);
export function JobAuditTrail({ currentUser, jobId }) {
export default function JobAuditTrail({ jobId }) {
const { t } = useTranslation();
const { loading, data, refetch } = useQuery(QUERY_AUDIT_TRAIL, {
const { loading, data } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { jobid: jobId },
skip: !jobId,
fetchPolicy: "network-only",
@@ -45,109 +34,15 @@ export function JobAuditTrail({ currentUser, jobId }) {
key: "operation",
},
];
const emailColumns = [
{
title: t("audit.fields.created"),
dataIndex: " created_at",
key: " created_at",
render: (text, record) => (
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
),
},
{
title: t("audit.fields.useremail"),
dataIndex: "useremail",
key: "useremail",
},
{
title: t("audit.fields.to"),
dataIndex: "to",
key: "to",
render: (text, record) =>
record.to &&
record.to.map((email, idx) => <Tag key={idx}>{email}</Tag>),
},
{
title: t("audit.fields.cc"),
dataIndex: "cc",
key: "cc",
render: (text, record) =>
record.cc &&
record.cc.map((email, idx) => <Tag key={idx}>{email}</Tag>),
},
{
title: t("audit.fields.subject"),
dataIndex: "subject",
key: "subject",
},
{
title: t("audit.fields.status"),
dataIndex: "status",
key: "status",
},
...(currentUser?.email.includes("@imex.")
? [
{
title: t("audit.fields.contents"),
dataIndex: "contents",
key: "contents",
width: "10%",
render: (text, record) => (
<Button
onClick={() => {
var win = window.open(
"",
"Title",
"toolbar=no,location=no,directories=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=780,height=400,"
);
win.document.body.innerHTML = record.contents;
}}
>
Preview
</Button>
),
},
]
: []),
];
return (
<Row gutter={[16, 16]}>
<Col span={24}>
<Card
title={t("jobs.labels.audit")}
extra={
<Button
onClick={() => {
refetch();
}}
>
<SyncOutlined />
</Button>
}
>
<Table
loading={loading}
columns={columns}
rowKey="id"
dataSource={data ? data.audit_trail : []}
/>
</Card>
</Col>
<Col span={24}>
<Card title={t("jobs.labels.emailaudit")}>
<Table
loading={loading}
columns={emailColumns}
rowKey="id"
dataSource={data ? data.email_audit_trail : []}
/>
</Card>
</Col>
</Row>
<Card title={t("jobs.labels.audit")}>
<Table
loading={loading}
columns={columns}
rowKey="id"
dataSource={data ? data.audit_trail : []}
/>
</Card>
);
}

View File

@@ -7,10 +7,7 @@ import { connect } from "react-redux";
import { useHistory, useLocation, useParams } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../../../firebase/firebase.utils";
import {
MARK_APPOINTMENT_ARRIVED,
MARK_LATEST_APPOINTMENT_ARRIVED,
} from "../../../../graphql/appointments.queries";
import { MARK_LATEST_APPOINTMENT_AS_ARRIVED } from "../../../../graphql/appointments.queries";
import { UPDATE_JOB } from "../../../../graphql/jobs.queries";
import {
selectBodyshop,
@@ -44,8 +41,7 @@ export function JobChecklistForm({
const { t } = useTranslation();
const [intakeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const [markAptArrived] = useMutation(MARK_APPOINTMENT_ARRIVED);
const [markLatestAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_ARRIVED);
const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED);
const [updateOwner] = useMutation(UPDATE_OWNER);
const { jobId } = useParams();
@@ -129,18 +125,6 @@ export function JobChecklistForm({
variables: { appointmentId: search.appointmentId },
});
if (!!appUpdate.errors) {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors),
}),
});
}
} else if (type === "intake" && !search.appointmentId) {
const appUpdate = await markLatestAptArrived({
variables: { jobId: jobId },
});
if (!!appUpdate.errors) {
notification["error"]({
message: t("checklist.errors.complete", {
@@ -149,7 +133,6 @@ export function JobChecklistForm({
});
}
}
if (type === "intake" && job.owner && job.owner.id) {
//Updae Owner Allow to Text
const updateOwnerResult = await updateOwner({
@@ -192,7 +175,12 @@ export function JobChecklistForm({
});
}
};
console.log(job, {
removeFromProduction: true,
actual_completion:
job && job.actual_completion && moment(job.actual_completion),
actual_delivery: job && job.actual_delivery && moment(job.actual_delivery),
});
return (
<Card
title={t("checklist.labels.checklist")}

View File

@@ -1,12 +1,11 @@
import { useQuery } from "@apollo/client";
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
import { Row, Col, Timeline, Typography, Space, Divider, Skeleton } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
import { DateFormatter } from "../../utils/DateFormatter";
import { Link } from "react-router-dom";
export default function JobLinesExpander({ jobline, jobid }) {
const { t } = useTranslation();
@@ -49,46 +48,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
)}
</Timeline>
</Col>
<Col md={24} lg={12}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline>
{data.billlines.length > 0 ? (
data.billlines.map((line) => (
<Timeline.Item key={line.id}>
<Row wrap>
<Col span={4}>
<Link
to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}
>
{line.bill.invoice_number}
</Link>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_price")}: `}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<DateFormatter>{line.bill.date}</DateFormatter>
</Col>
<Col span={4}> {line.bill.vendor.name}</Col>
</Row>
</Timeline.Item>
))
) : (
<Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
</Timeline>
</Col>
<Col md={24} lg={12}></Col>
</Row>
);
}

View File

@@ -115,10 +115,7 @@ export function JobLinesComponent({
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
ellipsis: true,
render: (text, record) =>
`${record.oem_partno || ""} ${
record.alt_partno ? `(${record.alt_partno})` : ""
}`.trim(),
render: (text, record) => record.oem_partno,
},
{
title: t("joblines.fields.op_code_desc"),
@@ -144,26 +141,11 @@ export function JobLinesComponent({
filters: [
{
text: t("jobs.labels.partsfilter"),
value: [
"PAN",
"PAC",
"PAR",
"PAL",
"PAA",
"PAM",
"PAP",
"PAS",
"PASL",
"PAG",
],
value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"],
},
{
text: t("joblines.fields.part_types.PAN"),
value: ["PAN"],
},
{
text: t("joblines.fields.part_types.PAP"),
value: ["PAP"],
value: ["PAN", "PAP"],
},
{
text: t("joblines.fields.part_types.PAL"),
@@ -173,29 +155,9 @@ export function JobLinesComponent({
text: t("joblines.fields.part_types.PAA"),
value: ["PAA"],
},
{
text: t("joblines.fields.part_types.PAG"),
value: ["PAG"],
},
{
text: t("joblines.fields.part_types.PAS"),
value: ["PAS"],
},
{
text: t("joblines.fields.part_types.PASL"),
value: ["PASL"],
},
{
text: t("joblines.fields.part_types.PAC"),
value: ["PAC"],
},
{
text: t("joblines.fields.part_types.PAR"),
value: ["PAR"],
},
{
text: t("joblines.fields.part_types.PAM"),
value: ["PAM"],
value: ["PAS", "PASL"],
},
],
onFilter: (value, record) => value.includes(record.part_type),
@@ -381,11 +343,7 @@ export function JobLinesComponent({
];
const handleTableChange = (pagination, filters, sorter) => {
setState((state) => ({
...state,
filteredInfo: filters,
sortedInfo: sorter,
}));
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const handleMark = (e) => {
@@ -503,12 +461,7 @@ export function JobLinesComponent({
context: {
jobId: job.id,
job: job,
linesToOrder: selectedLines.map((l) => ({
...l,
oem_partno: `${l.oem_partno || ""} ${
l.alt_partno ? `(${l.alt_partno})` : ""
}`.trim(),
})),
linesToOrder: selectedLines,
},
});
@@ -521,24 +474,12 @@ export function JobLinesComponent({
</Button>
<Button
onClick={() => {
setState((state) => ({
setState({
...state,
filteredInfo: {
...state.filteredInfo,
part_type: [
"PAN",
"PAC",
"PAR",
"PAL",
"PAA",
"PAM",
"PAP",
"PAS",
"PASL",
"PAG",
],
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL,PAG"],
},
}));
});
}}
>
<FilterFilled /> {t("jobs.actions.filterpartsonly")}

View File

@@ -66,8 +66,10 @@ export function JobLineConvertToLabor({
const newAdjustments = _.cloneDeep(
existingAdjustments.data.jobs_by_pk.lbr_adjustments
);
const adjustment = calculateAdjustment({ mod_lbr_ty, job, jobline });
newAdjustments[mod_lbr_ty] = (newAdjustments[mod_lbr_ty] || 0) + adjustment;
newAdjustments[mod_lbr_ty] =
(newAdjustments[mod_lbr_ty] || 0) +
calculateAdjustment({ mod_lbr_ty, job, jobline });
const jobUpdate = client.mutate({
mutation: UPDATE_JOB,
@@ -81,13 +83,7 @@ export function JobLineConvertToLabor({
mutation: UPDATE_JOB_LINE,
variables: {
lineId: jobline.id,
line: {
convertedtolbr: true,
convertedtolbr_data: {
mod_lbr_ty: mod_lbr_ty,
mod_lb_hrs: adjustment,
},
},
line: { convertedtolbr: true },
},
});

View File

@@ -1,5 +1,4 @@
import { useMutation, useLazyQuery } from "@apollo/client";
import { CheckCircleOutlined } from "@ant-design/icons";
import {
Button,
Card,
@@ -142,12 +141,6 @@ export default function ScoreboardAddButton({
</Form>
)}
</div>
{entryData && entryData.scoreboard && entryData.scoreboard[0] && (
<Space>
<CheckCircleOutlined style={{ color: "green" }} />
<span>{t("jobs.labels.alreadyaddedtoscoreboard")}</span>
</Space>
)}
</Card>
);

View File

@@ -1,4 +1,3 @@
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import { useMutation } from "@apollo/client";
import { Button, Form, notification } from "antd";
import React, { useEffect, useState } from "react";
@@ -38,8 +37,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
setLoading(true);
const result = await updateJob({
variables: { jobId: job.id, job: values },
refetchQueries: ['GET_JOB_BY_PK'],
awaitRefetchQueries:true
});
const changedAuditFields = form.getFieldsValue(
@@ -68,8 +65,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
}),
});
}
form.resetFields();
form.resetFields();
setLoading(false);
//Get the owner details, populate it all back into the job.
};
@@ -92,7 +87,6 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
: null,
}}
>
<FormFieldsChanged form={form} />
<LayoutFormRow header={t("jobs.forms.estdates")}>
<Form.Item
label={t("jobs.fields.date_estimated")}

View File

@@ -95,7 +95,7 @@ mutation UNVOID_JOB($jobId: uuid!) {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.admin_jobunvoid(),
operation: AuditTrailMapping.admin_unvoicejob(),
});
} else {
notification["error"]({

View File

@@ -45,11 +45,9 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
return acc + generateUpdateQuery(value, idx);
}, "");
const removeQueries = existingLines
.filter((l) => !l.manual_line)
.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
const removeQueries = existingLines.reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx);
}, "");
console.log(insertQueries, updateQueries, removeQueries);
if ((insertQueries + updateQueries + removeQueries).trim() === "") {

View File

@@ -205,7 +205,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseLines);
const HasBeenConvertedTolabor = ({ value }) => {
const { t } = useTranslation();
console.log(value);
if (!value) return null;
return (
<Tooltip title={t("joblines.labels.convertedtolabor")}>

View File

@@ -1,163 +0,0 @@
import { useMutation } from "@apollo/client";
import {
Button,
Card,
Form,
Input,
Menu,
notification,
Popover,
Select,
Space,
} from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDetailHeaderAddEvent);
export function JobsDetailHeaderAddEvent({ bodyshop, jobid, ...props }) {
const { t } = useTranslation();
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
const handleFinish = async (values) => {
logImEXEvent("schedule_manual_event");
setLoading(true);
try {
insertAppointment({
variables: {
apt: { ...values, isintake: false, jobid, bodyshopid: bodyshop.id },
},
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
});
notification.open({
type: "success",
message: t("appointments.successes.created"),
});
} catch (error) {
console.log(error);
} finally {
setLoading(false);
setVisibility(false);
}
};
const overlay = (
<Card>
<div>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<Form.Item
label={t("appointments.fields.title")}
name="title"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item label={t("appointments.fields.note")} name="note">
<Input />
</Form.Item>
<Form.Item
label={t("appointments.fields.start")}
name="start"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDateTimePickerComponent
onBlur={() => {
const start = form.getFieldValue("start");
form.setFieldsValue({ end: start.add(30, "minutes") });
}}
/>
</Form.Item>
<Form.Item
label={t("appointments.fields.end")}
name="end"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
async validator(rule, value) {
if (value) {
const { start } = form.getFieldsValue();
if (moment(start).isAfter(moment(value))) {
return Promise.reject(
t("employees.labels.endmustbeafterstart")
);
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
},
}),
]}
>
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item label={t("appointments.fields.color")} name="color">
<Select>
{bodyshop.appt_colors.map((col, idx) => (
<Select.Option key={idx} value={col.color.hex}>
{col.label}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button type="primary" htmlType="submit" loading={loading}>
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisibility(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
</Card>
);
const handleClick = (e) => {
setVisibility(true);
};
return (
<Popover content={overlay} visible={visibility}>
<Menu.Item {...props} onClick={handleClick}>
{t("appointments.labels.manualevent")}
</Menu.Item>
</Popover>
);
}

View File

@@ -15,7 +15,6 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import JobsDetailHeaderActionsAddevent from "./jobs-detail-header-actions.addevent";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
@@ -422,7 +421,6 @@ export function JobsDetailHeaderActions({
</Popconfirm>
</Menu.Item>
)}
<JobsDetailHeaderActionsAddevent jobid={job.id} />
{!jobRO && job.converted && (
<Menu.Item>
<Popconfirm

View File

@@ -1,11 +1,10 @@
import { Card, Col, Row, Space, Tag } from "antd";
import {
WarningFilled,
ExclamationCircleFilled,
PauseCircleOutlined,
WarningFilled,
BranchesOutlined,
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
import React from "react";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -14,17 +13,17 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -57,6 +56,12 @@ const colSpan = {
export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation();
const jobInPostProduction = useMemo(() => {
return bodyshop.md_ro_statuses.post_production_statuses.includes(
job.status
);
}, [job.status, bodyshop.md_ro_statuses.post_production_statuses]);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
@@ -79,13 +84,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{job.iouparent && (
<Link to={`/manage/jobs/${job.iouparent}`}>
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
</Link>
)}
{job.production_vars && job.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
@@ -131,10 +129,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
</DataLabel>
)}
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
{(job.inproduction || jobInPostProduction) && (
<DataLabel label={t("jobs.fields.production_vars.note")}>
<ProductionListColumnProductionNote record={job} />
</DataLabel>
)}
<Space>
{job.special_coverage_policy && (
<Tag color="tomato">
@@ -182,11 +181,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel key="4" label={t("owners.fields.ownr_ea")}>
{job.ownr_ea || ""}
</DataLabel>
{job.owner?.tax_number && (
<DataLabel key="5" label={t("owners.fields.tax_number")}>
{job.owner?.tax_number || ""}
</DataLabel>
)}
</div>
</Card>
</Col>

View File

@@ -1,11 +1,14 @@
import { useApolloClient } from "@apollo/client";
import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Form, notification, Popover, Space } from "antd";
import axios from "axios";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { GET_DOC_SIZE_BY_JOB } from "../../graphql/documents.queries";
import {
GET_DOC_SIZE_BY_JOB,
UPDATE_DOCUMENT,
} from "../../graphql/documents.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobSearchSelect from "../job-search-select/job-search-select.component";
@@ -20,11 +23,7 @@ export default connect(
mapDispatchToProps
)(JobsDocumentsGalleryReassign);
export function JobsDocumentsGalleryReassign({
bodyshop,
galleryImages,
callback,
}) {
export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
const { t } = useTranslation();
const [form] = Form.useForm();
@@ -37,33 +36,34 @@ export function JobsDocumentsGalleryReassign({
const client = useApolloClient();
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [updateDocument] = useMutation(UPDATE_DOCUMENT);
// const updateImage = async (i, jobid) => {
// //Move the cloudinary image
const updateImage = async (i, jobid) => {
//Move the cloudinary image
// //Update it in the database.
// const result = await updateDocument({
// variables: {
// id: i.id,
// document: {
// key: i.public_id,
// jobid: jobid,
// },
// },
// });
//Update it in the database.
const result = await updateDocument({
variables: {
id: i.id,
document: {
key: i.public_id,
jobid: jobid,
},
},
});
// if (!!result.errors) {
// notification["error"]({
// message: t("documents.errors.updating", {
// message: JSON.stringify(result.errors),
// }),
// });
// } else {
// notification["success"]({
// message: t("documents.successes.updated"),
// });
// }
// };
if (!!result.errors) {
notification["error"]({
message: t("documents.errors.updating", {
message: JSON.stringify(result.errors),
}),
});
} else {
notification["success"]({
message: t("documents.successes.updated"),
});
}
};
const handleFinish = async ({ jobid }) => {
setLoading(true);
@@ -96,7 +96,6 @@ export function JobsDocumentsGalleryReassign({
}
const res = await axios.post("/media/rename", {
tojobid: jobid,
documents: selectedImages.map((i) => {
//Need to check if the current key folder is null, or another job.
const currentKeys = i.key.split("/");
@@ -111,21 +110,24 @@ export function JobsDocumentsGalleryReassign({
};
}),
});
//Add in confirmation & errors.
if (callback) callback();
if (res.errors) {
notification["error"]({
message: t("documents.errors.updating", {
message: JSON.stringify(res.errors),
}),
res.data
.filter((d) => d.error)
.forEach((d) => {
notification["error"]({ message: t("documents.errors.updating") });
console.error("Error updating job document", d);
});
}
if (!res.mutationResult?.errors) {
notification["success"]({
message: t("documents.successes.updated"),
const proms = [];
res.data
.filter((d) => !d.error)
.forEach((d) => {
proms.push(updateImage(d, jobid));
});
}
await Promise.all(proms);
setVisible(false);
setLoading(false);
};

View File

@@ -125,10 +125,7 @@ function JobsDocumentsComponent({
deletionCallback={billsCallback || refetch}
/>
{!billId && (
<JobsDocumentsGalleryReassign
galleryImages={galleryImages}
callback={refetch}
/>
<JobsDocumentsGalleryReassign galleryImages={galleryImages} />
)}
</Space>
</Col>

View File

@@ -1,9 +1,11 @@
import { QuestionCircleOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, notification, Popconfirm } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { DELETE_DOCUMENTS } from "../../graphql/documents.queries";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
export default function JobsDocumentsDeleteButton({
@@ -11,7 +13,7 @@ export default function JobsDocumentsDeleteButton({
deletionCallback,
}) {
const { t } = useTranslation();
const [deleteDocument] = useMutation(DELETE_DOCUMENTS);
const imagesToDelete = [
...galleryImages.images.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected),
@@ -25,10 +27,31 @@ export default function JobsDocumentsDeleteButton({
ids: imagesToDelete,
});
if (res.data.error) {
const successfulDeletes = [];
res.data.forEach((resType) => {
Object.keys(resType.deleted).forEach((key) => {
if (resType.deleted[key] !== "deleted") {
notification["error"]({
message: t("documents.errors.deleting_cloudinary", {
message: JSON.stringify(resType.deleted[key]),
}),
});
} else {
successfulDeletes.push(key.replace(/\.[^/.]+$/, ""));
}
});
});
const delres = await deleteDocument({
variables: {
ids: imagesToDelete
.filter((i) => successfulDeletes.includes(i.key))
.map((i) => i.id),
},
});
if (delres.errors) {
notification["error"]({
message: t("documents.errors.deleting", {
error: JSON.stringify(res.data.error.response.errors),
message: JSON.stringify(delres.errors),
}),
});
} else {

View File

@@ -1,5 +1,5 @@
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
import { Alert, Button, Card, Space } from "antd";
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";
@@ -14,7 +14,6 @@ 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 JobsDocumentsLocalDeleteButton from "./jobs-documents-local-gallery.delete.component";
import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.download";
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
@@ -58,32 +57,6 @@ export function JobsDocumentsLocalGallery({
}
}
}, [job, invoice_number, getJobMedia, getBillMedia]);
let optimized;
const jobMedia =
allMedia && allMedia[job.id]
? allMedia[job.id].reduce(
(acc, val) => {
if (
val.type &&
val.type.mime &&
val.type.mime.startsWith("image")
) {
acc.images.push({
...val,
...(val.optimized && { src: val.optimized, fullsize: val.src }),
});
if (val.optimized) optimized = true;
} else {
acc.other.push({
...val,
tags: [{ value: val.filename, title: val.filename }],
});
}
return acc;
},
{ images: [], other: [] }
)
: { images: [], other: [] };
return (
<div>
@@ -107,70 +80,30 @@ export function JobsDocumentsLocalGallery({
<JobsDocumentsLocalGalleryReassign jobid={job.id} />
<JobsDocumentsLocalGallerySelectAllComponent jobid={job.id} />
<JobsLocalGalleryDownloadButton job={job} />
<JobsDocumentsLocalDeleteButton jobid={job.id} />
</Space>
<Card>
<DocumentsLocalUploadComponent
job={job}
invoice_number={invoice_number}
vendorid={vendorid}
allowAllTypes
/>
</Card>
<Card title={t("jobs.labels.documents-images")}>
<Gallery
images={jobMedia.images}
images={(allMedia && allMedia[job.id]) || []}
backdropClosesModal={true}
onSelectImage={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
{...(optimized && {
customControls: [
<Alert
style={{ margin: "4px" }}
message={t("documents.labels.optimizedimage")}
type="success"
/>,
],
})}
onClickImage={(props) => {
const media = allMedia[job.id].find(
(m) => m.optimized === props.target.src
);
window.open(
media ? media.src : props.target.src,
props.target.src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
/>
</Card>
<Card title={t("jobs.labels.documents-other")}>
<Gallery
images={jobMedia.other}
backdropClosesModal={true}
enableLightbox={false}
thumbnailStyle={() => {
return {
backgroundImage: <FileExcelFilled />,
height: "100%",
width: "100%",
cursor: "pointer",
};
}}
onClickThumbnail={(index) => {
window.open(
jobMedia.other[index].src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelectImage={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
/>
</Card>
</div>
);
}

View File

@@ -1,81 +0,0 @@
import { QuestionCircleOutlined } from "@ant-design/icons";
import { Button, notification, Popconfirm } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import cleanAxios from "../../utils/CleanAxios";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
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";
const mapStateToProps = createStructuredSelector({
allMedia: selectAllMedia,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
getJobMedia: (id) => dispatch(getJobMedia(id)),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDocumentsLocalDeleteButton);
export function JobsDocumentsLocalDeleteButton({
bodyshop,
getJobMedia,
allMedia,
jobid,
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleDelete = async () => {
logImEXEvent("job_documents_delete");
setLoading(true);
const delres = await cleanAxios.post(
`${bodyshop.localmediaserverhttp}/jobs/delete`,
{
jobid: jobid,
files: ((allMedia && allMedia[jobid]) || [])
.filter((i) => i.isSelected)
.map((i) => i.filename),
},
{ headers: { ims_token: bodyshop.localmediatoken } }
);
if (delres.errors) {
notification["error"]({
message: t("documents.errors.deleting", {
message: JSON.stringify(delres.errors),
}),
});
} else {
notification.open({
key: "docdeletedsuccesfully",
type: "success",
message: t("documents.successes.delete"),
});
}
getJobMedia(jobid);
setLoading(false);
};
return (
<Popconfirm
icon={<QuestionCircleOutlined style={{ color: "red" }} />}
onConfirm={handleDelete}
title={t("documents.labels.confirmdelete")}
okText={t("general.actions.delete")}
okButtonProps={{ type: "danger" }}
cancelText={t("general.actions.cancel")}
>
<Button loading={loading}>{t("documents.actions.delete")}</Button>
</Popconfirm>
);
}

View File

@@ -1,79 +0,0 @@
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 {
getJobMedia,
toggleMediaSelected,
} from "../../redux/media/media.actions";
import { selectAllMedia } from "../../redux/media/media.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
allMedia: selectAllMedia,
});
const mapDispatchToProps = (dispatch) => ({
getJobMedia: (id) => dispatch(getJobMedia(id)),
toggleMediaSelected: ({ jobid, filename }) =>
dispatch(toggleMediaSelected({ jobid, filename })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobDocumentsLocalGalleryExternal);
function JobDocumentsLocalGalleryExternal({
jobId,
externalMediaState,
getJobMedia,
toggleMediaSelected,
allMedia,
}) {
const [galleryImages, setgalleryImages] = externalMediaState;
const { t } = useTranslation();
useEffect(() => {
if ( jobId) {
getJobMedia(jobId);
}
}, [jobId, getJobMedia]);
useEffect(() => {
let documents =
allMedia && allMedia[jobId]
? allMedia[jobId].reduce((acc, val) => {
if (
val.type &&
val.type.mime &&
val.type.mime.startsWith("image")
) {
acc.push(val);
}
return acc;
}, [])
: [];
setgalleryImages(documents);
}, [allMedia, jobId, setgalleryImages, t]);
return (
<div className="clearfix">
<Gallery
images={galleryImages}
backdropClosesModal={true}
onSelectImage={(index, image) => {
setgalleryImages(
galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g
)
);
}}
/>
</div>
);
}

View File

@@ -2,10 +2,9 @@ import {
SyncOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
BranchesOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
import { Button, Card, Grid, Input, Space, Table } from "antd";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -78,12 +77,6 @@ export function JobsList({ bodyshop }) {
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
@@ -129,11 +122,6 @@ export function JobsList({ bodyshop }) {
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
@@ -276,32 +264,6 @@ export function JobsList({ bodyshop }) {
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",

View File

@@ -14,7 +14,6 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import useLocalStorage from "../../utils/useLocalStorage";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
@@ -41,8 +40,6 @@ export function JobNotesComponent({
relatedRos,
}) {
const { t } = useTranslation();
const [filter, setFilter] = useLocalStorage("filter_job_notes_icons", null);
const Templates = TemplateList("job_special", {
ro_number,
});
@@ -53,18 +50,6 @@ export function JobNotesComponent({
dataIndex: "icons",
key: "icons",
width: 80,
filteredValue: filter?.icons || null,
filters: [
{
text: t("notes.labels.usernotes"),
value: false,
},
{
text: t("notes.labels.systemnotes"),
value: true,
},
],
onFilter: (value, record) => record.audit === value,
render: (text, record) => (
<span>
{record.critical ? (
@@ -146,10 +131,6 @@ export function JobNotesComponent({
},
];
const handleTableChange = (pagination, filters, sorter) => {
setFilter(filters);
};
return (
<div>
<LayoutFormRow>
@@ -185,7 +166,6 @@ export function JobNotesComponent({
columns={columns}
rowKey="id"
dataSource={data}
onChange={handleTableChange}
/>
</Card>
</div>

View File

@@ -2,10 +2,9 @@ import {
ExclamationCircleFilled,
PauseCircleOutlined,
SyncOutlined,
BranchesOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
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";
@@ -90,12 +89,6 @@ export function JobsReadyList({ bodyshop }) {
(j.v_model_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.v_make_desc || "")
.toLowerCase()
.includes(searchText.toLowerCase())
@@ -141,11 +134,6 @@ export function JobsReadyList({ bodyshop }) {
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),
@@ -288,32 +276,6 @@ export function JobsReadyList({ bodyshop }) {
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",

View File

@@ -1,13 +1,12 @@
import { EditFilled } from "@ant-design/icons";
import { Card, Col, Row, Space, Table, Typography } from "antd";
import { Card, Col, Row, Space, Table } from "antd";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
import "./labor-allocations-table.styles.scss";
@@ -45,10 +44,10 @@ export function LaborAllocationsTable({
if (!jobId) setTotals([]);
}, [joblines, timetickets, bodyshop, adjustments, jobId]);
const convertedLines = useMemo(
() => joblines && joblines.filter((j) => j.convertedtolbr),
[joblines]
);
// const convertedLines = useMemo(
// () => joblines && joblines.filter((j) => j.convertedtolbr),
// [joblines]
// );
const columns = [
{
@@ -121,86 +120,57 @@ export function LaborAllocationsTable({
),
},
];
const convertedTableCols = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
ellipsis: true,
},
{
title: t("joblines.fields.op_code_desc"),
dataIndex: "op_code_desc",
key: "op_code_desc",
ellipsis: true,
render: (text, record) =>
`${record.op_code_desc || ""}${
record.alt_partm ? ` ${record.alt_partm}` : ""
}`,
},
// const convertedTableCols = [
// {
// title: t("joblines.fields.line_desc"),
// dataIndex: "line_desc",
// key: "line_desc",
// ellipsis: true,
// },
// {
// title: t("joblines.fields.op_code_desc"),
// dataIndex: "op_code_desc",
// key: "op_code_desc",
// ellipsis: true,
// render: (text, record) =>
// `${record.op_code_desc || ""}${
// record.alt_partm ? ` ${record.alt_partm}` : ""
// }`,
// },
{
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
ellipsis: true,
render: (text, record) => (
<>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</>
),
},
{
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
},
{
title: t("joblines.fields.mod_lbr_ty"),
dataIndex: "conv_mod_lbr_ty",
key: "conv_mod_lbr_ty",
render: (text, record) =>
record.convertedtolbr_data && record.convertedtolbr_data.mod_lbr_ty,
},
{
title: t("joblines.fields.mod_lb_hrs"),
dataIndex: "conv_mod_lb_hrs",
key: "conv_mod_lb_hrs",
render: (text, record) =>
record.convertedtolbr_data &&
record.convertedtolbr_data.mod_lb_hrs &&
record.convertedtolbr_data.mod_lb_hrs.toFixed(1),
},
];
// {
// title: t("joblines.fields.act_price"),
// dataIndex: "act_price",
// key: "act_price",
// ellipsis: true,
// render: (text, record) => (
// <>
// <CurrencyFormatter>
// {record.db_ref === "900510" || record.db_ref === "900511"
// ? record.prt_dsmk_m
// : record.act_price}
// </CurrencyFormatter>
// {record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
// <span
// style={{ marginLeft: ".2rem" }}
// >{`(${record.prt_dsmk_p}%)`}</span>
// ) : (
// <></>
// )}
// </>
// ),
// },
// {
// title: t("joblines.fields.part_qty"),
// dataIndex: "part_qty",
// key: "part_qty",
// },
// ];
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const summary =
totals &&
totals.reduce(
(acc, val) => {
acc.hrs_total += val.total;
acc.hrs_claimed += val.claimed;
acc.adjustments += val.adjustments;
acc.difference += val.difference;
return acc;
},
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
);
return (
<Row gutter={[16, 16]}>
<Col span={24}>
@@ -214,45 +184,26 @@ export function LaborAllocationsTable({
scroll={{
x: true,
}}
summary={() => (
<Table.Summary.Row>
<Table.Summary.Cell>
<Typography.Title level={4}>
{t("general.labels.totals")}
</Typography.Title>
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.hrs_total.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.hrs_claimed.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.adjustments.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{summary.difference.toFixed(1)}
</Table.Summary.Cell>
</Table.Summary.Row>
)}
/>
</Card>
</Col>
{convertedLines && convertedLines.length > 0 && (
<Col span={24}>
<Card title={t("jobs.labels.convertedtolabor")}>
<Table
columns={convertedTableCols}
rowKey="id"
pagination={false}
dataSource={convertedLines}
scroll={{
x: true,
}}
/>
</Card>
</Col>
)}
{
// convertedLines && convertedLines.length > 0 && (
// <Col span={24}>
// <Card title={t("jobs.labels.convertedtolabor")}>
// <Table
// columns={convertedTableCols}
// rowKey="id"
// pagination={false}
// dataSource={convertedLines}
// scroll={{
// x: true,
// }}
// />
// </Card>
// </Col>
// )
}
</Row>
);
}

View File

@@ -6,17 +6,12 @@ export const CalculateAllocationsTotals = (
timetickets,
adjustments = []
) => {
console.log(
"🚀 ~ file: labor-allocations-table.utility.js ~ line 9 ~ adjustments",
adjustments
);
const responsibilitycenters = bodyshop.md_responsibility_centers;
const jobCodes = joblines.map((item) => item.mod_lbr_ty);
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
const ticketCodes = timetickets.map((item) => item.ciecacode);
//.filter((value, index, self) => self.indexOf(value) === index && !!value);
const adjustmentCodes = Object.keys(adjustments);
const allCodes = [...jobCodes, ...ticketCodes, ...adjustmentCodes].filter(
const allCodes = [...jobCodes, ...ticketCodes].filter(
(value, index, self) => self.indexOf(value) === index && !!value
);
@@ -39,7 +34,7 @@ export const CalculateAllocationsTotals = (
}, 0),
};
r.difference = r.total + r.adjustments - r.claimed;
r.difference = (r.total + r.adjustments - r.claimed).toFixed(2);
acc.push(r);
return acc;
}, []);

View File

@@ -94,12 +94,6 @@ export default function OwnerDetailFormComponent({ form, loading }) {
>
<Input />
</Form.Item>
<Form.Item
label={t("owners.fields.tax_number")}
name="tax_number"
>
<Input />
</Form.Item>
</LayoutFormRow>
<Form.Item label={t("owners.fields.note")} name="note">
<Input.TextArea rows={4} />

View File

@@ -20,10 +20,9 @@ function OwnerDetailFormContainer({ owner, refetch }) {
if (!!result.errors) {
notification["error"]({
message: t("owners.errors.saving", {
error: JSON.stringify(result.errors),
message: JSON.stringify(result.errors),
}),
});
setLoading(false);
return;
}

View File

@@ -12,24 +12,24 @@ export default connect(mapStateToProps, mapDispatchToProps)(OwnerNameDisplay);
export function OwnerNameDisplay({ bodyshop, ownerObject }) {
const emptyTest =
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
return "N/A";
if (bodyshop.last_name_first)
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
ownerObject?.ownr_co_nm || ""
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
}
export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
const emptyTest =
ownerObject?.ownr_fn + ownerObject?.ownr_ln + ownerObject?.ownr_co_nm;
ownerObject.ownr_fn + ownerObject.ownr_ln + ownerObject.ownr_co_nm;
if (!emptyTest || emptyTest === "null" || emptyTest.trim() === "")
return "N/A";
@@ -37,11 +37,11 @@ export function OwnerNameDisplayFunction(ownerObject, forceFirstLast = false) {
const rdxStore = store.getState();
if (rdxStore.user.bodyshop.last_name_first && !forceFirstLast)
return `${ownerObject?.ownr_ln || ""}, ${ownerObject?.ownr_fn || ""} ${
ownerObject?.ownr_co_nm || ""
return `${ownerObject.ownr_ln || ""}, ${ownerObject.ownr_fn || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
return `${ownerObject?.ownr_fn || ""} ${ownerObject?.ownr_ln || ""} ${
ownerObject?.ownr_co_nm || ""
return `${ownerObject.ownr_fn || ""} ${ownerObject.ownr_ln || ""} ${
ownerObject.ownr_co_nm || ""
}`.trim();
}

View File

@@ -72,8 +72,7 @@ export function PartsOrderListTableComponent({
? bpoints[selectedBreakpoint[0]]
: "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job });
const Templates = TemplateList("partsorder");
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},

View File

@@ -1,97 +0,0 @@
import { DownOutlined } from "@ant-design/icons";
import { Dropdown, InputNumber, Menu, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
export default function PartsOrderModalPriceChange({ form, field }) {
const { t } = useTranslation();
const menu = (
<Menu
onClick={({ key }) => {
if (key === "custom") return;
const values = form.getFieldsValue();
const { parts_order_lines } = values;
form.setFieldsValue({
parts_order_lines: {
data: parts_order_lines.data.map((p, idx) => {
if (idx !== field.name) return p;
return {
...p,
act_price: (p.act_price || 0) * ((100 - key) / 100),
};
}),
},
});
}}
items={[
{
key: "5",
label: t("parts_orders.labels.discount", { percent: "5%" }),
},
{
key: "10",
label: t("parts_orders.labels.discount", { percent: "10%" }),
},
{
key: "15",
label: t("parts_orders.labels.discount", { percent: "15%" }),
},
{
key: "20",
label: t("parts_orders.labels.discount", { percent: "20%" }),
},
{
key: "25",
label: t("parts_orders.labels.discount", { percent: "25%" }),
},
{
key: "custom",
label: (
<InputNumber
onClick={(e) => e.stopPropagation()}
addonAfter="%"
onKeyUp={(e) => {
if (e.key === "Enter") {
const values = form.getFieldsValue();
const { parts_order_lines } = values;
form.setFieldsValue({
parts_order_lines: {
data: parts_order_lines.data.map((p, idx) => {
if (idx !== field.name) return p;
console.log(
p,
e.target.value,
(p.act_price || 0) *
((100 - (e.target.value || 0)) / 100)
);
return {
...p,
act_price:
(p.act_price || 0) *
((100 - (e.target.value || 0)) / 100),
};
}),
},
});
e.target.value = 0;
}
}}
min={0}
max={100}
/>
),
},
]}
/>
);
return (
<Dropdown overlay={menu} trigger="click">
<Space>
%
<DownOutlined />
</Space>
</Dropdown>
);
}

View File

@@ -23,7 +23,6 @@ import CurrencyInput from "../form-items-formatted/currency-form-item.component"
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -134,21 +133,6 @@ export function PartsOrderModalComponent({
<Checkbox />
</Form.Item>
)}
<Form.Item
name="order_type"
initialValue="parts_order"
label={t("parts_orders.labels.order_type")}
>
<Radio.Group disabled={sendType === "oec"}>
<Radio value={"parts_order"}>
{t("parts_orders.labels.parts_order")}
</Radio>
<Radio value={"sublet"}>
{t("parts_orders.labels.sublet_order")}
</Radio>
</Radio.Group>
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">
{t("parts_orders.labels.inthisorder")}
@@ -262,14 +246,7 @@ export function PartsOrderModalComponent({
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput
addonBefore={
<PartsOrderModalPriceChange
form={form}
field={field}
/>
}
/>
<CurrencyInput />
</Form.Item>
{isReturn && (
<Form.Item

View File

@@ -94,7 +94,6 @@ export function PartsOrderModalContainer({
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async ({
order_type,
removefrompartsqueue,
is_quote,
...values
@@ -103,46 +102,47 @@ export function PartsOrderModalContainer({
setSaving(true);
let insertResult;
insertResult = await insertPartOrder({
variables: {
po: [
{
...values,
order_date: moment().format("YYYY-MM-DD"),
orderedby: currentUser.email,
jobid: jobId,
user_email: currentUser.email,
return: isReturn,
status: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
insertResult = await insertPartOrder({
variables: {
po: [
{
...values,
order_date: moment().format("YYYY-MM-DD"),
orderedby: currentUser.email,
jobid: jobId,
user_email: currentUser.email,
return: isReturn,
status: is_quote
? bodyshop.md_order_statuses.default_quote || "Quote"
: bodyshop.md_order_statuses.default_ordered || "Ordered*",
},
],
},
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
if (!!insertResult.errors) {
notification["error"]({
message: t("parts_orders.errors.creating"),
description: JSON.stringify(insertResult.errors),
});
return;
}
notification["success"]({
message: values.isReturn
? t("parts_orders.successes.return_created")
: t("parts_orders.successes.created"),
});
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
const jobLinesResult = await updateJobLines({
variables: {
@@ -228,9 +228,7 @@ export function PartsOrderModalContainer({
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
: Templates.parts_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
@@ -240,9 +238,7 @@ export function PartsOrderModalContainer({
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
: order_type === "parts_order"
? Templates.parts_order.subject
: Templates.sublet_order.subject,
: Templates.parts_order.subject,
},
"e",
jobId
@@ -252,9 +248,7 @@ export function PartsOrderModalContainer({
{
name: isReturn
? Templates.parts_return_slip.key
: order_type === "parts_order"
? Templates.parts_order.key
: Templates.sublet_order.key,
: Templates.parts_order.key,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},

View File

@@ -14,7 +14,6 @@ import {
import { logImEXEvent } from "../../firebase/firebase.utils";
import _ from "lodash";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -177,13 +176,6 @@ export function PayableExportAll({
setLoading(false);
};
if (bodyshop.pbs_serialnumber)
return (
<Link to={{ state: { billids }, pathname: `/manage/dmsap` }}>
<Button>{t("jobs.actions.export")}</Button>
</Link>
);
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}

View File

@@ -13,7 +13,6 @@ import {
} from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -180,13 +179,6 @@ export function PayableExportButton({
setLoading(false);
};
if (bodyshop.pbs_serialnumber)
return (
<Link to={{ state: { billids: [billId] }, pathname: `/manage/dmsap` }}>
<Button>{t("jobs.actions.export")}</Button>
</Link>
);
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}

View File

@@ -1,5 +1,5 @@
import { gql, useMutation } from "@apollo/client";
import { Button, notification, Popconfirm } from "antd";
import { Button, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -34,7 +34,6 @@ export function BillMarkSelectedExported({
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [updateBill] = useMutation(gql`
mutation UPDATE_BILL($billIds: [uuid!]!) {
@@ -85,24 +84,11 @@ export function BillMarkSelectedExported({
completedCallback && completedCallback([]);
setLoading(false);
refetch && refetch();
setVisible(false);
};
return (
<Popconfirm
visible={visible}
title={t("general.labels.areyousure")}
onCancel={() => setVisible(false)}
onConfirm={handleUpdate}
disabled={disabled}
>
<Button
loading={loading}
disabled={disabled}
onClick={() => setVisible(true)}
>
{t("bills.labels.markexported")}
</Button>
</Popconfirm>
<Button loading={loading} disabled={disabled} onClick={handleUpdate}>
{t("bills.labels.markexported")}
</Button>
);
}

View File

@@ -1,4 +1,3 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { CardElement } from "@stripe/react-stripe-js";
import { Checkbox, Form, Input, Radio, Select } from "antd";
import React from "react";
@@ -24,11 +23,6 @@ export function PaymentFormComponent({
disabled,
}) {
const [stripeState, setStripeState] = stripeStateArr;
const { Qb_Multi_Ar } = useTreatments(
["Qb_Multi_Ar"],
{},
bodyshop && bodyshop.imexshopid
);
const { t } = useTranslation();
const handleStripeChange = (e) => {
@@ -112,21 +106,9 @@ export function PaymentFormComponent({
<Select.Option value={t("payments.labels.customer")}>
{t("payments.labels.customer")}
</Select.Option>
{Qb_Multi_Ar.treatment === "on" ? (
<>
<Select.OptGroup label={t("payments.labels.external")}>
{bodyshop.md_ins_cos.map((i, idx) => (
<Select.Option key={idx} value={i.name}>
{i.name}
</Select.Option>
))}
</Select.OptGroup>
</>
) : (
<Select.Option value={t("payments.labels.insurance")}>
{t("payments.labels.insurance")}
</Select.Option>
)}
<Select.Option value={t("payments.labels.insurance")}>
{t("payments.labels.insurance")}
</Select.Option>
</Select>
</Form.Item>

View File

@@ -1,111 +0,0 @@
import { gql, useMutation } from "@apollo/client";
import { Button, notification, Popconfirm } 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
)(PaymentMarkSelectedExported);
export function PaymentMarkSelectedExported({
bodyshop,
currentUser,
paymentIds,
disabled,
loadingCallback,
completedCallback,
refetch,
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [updatePayments] = useMutation(gql`
mutation UPDATE_PAYMENTS($paymentIds: [uuid!]!, $exportedat: timestamptz!) {
update_payments(
where: { id: { _in: $paymentIds } }
_set: { exportedat: $exportedat }
) {
returning {
id
exportedat
}
}
}
`);
const handleUpdate = async () => {
setLoading(true);
loadingCallback(true);
const result = await updatePayments({
variables: { paymentIds: paymentIds, exportedat: new Date() },
update(cache) {},
});
await insertExportLog({
variables: {
logs: paymentIds.map((id) => {
return {
bodyshopid: bodyshop.id,
paymentid: id,
successful: true,
message: JSON.stringify([t("general.labels.markedexported")]),
useremail: currentUser.email,
};
}),
},
});
if (!result.errors) {
notification["success"]({
message: t("payments.successes.markexported"),
});
} else {
notification["error"]({
message: t("bills.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
loadingCallback(false);
completedCallback && completedCallback([]);
setLoading(false);
refetch && refetch();
setVisible(false);
};
return (
<Popconfirm
visible={visible}
title={t("general.labels.areyousure")}
onCancel={() => setVisible(false)}
onConfirm={handleUpdate}
disabled={disabled}
>
<Button
loading={loading}
disabled={disabled}
onClick={() => setVisible(true)}
>
{t("bills.labels.markexported")}
</Button>
</Popconfirm>
);
}

View File

@@ -1,4 +1,4 @@
import { Button, Card, Form, InputNumber, Popover, Radio } from "antd";
import { Button, Card, Form, InputNumber, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -24,8 +24,7 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const handleOk = (e) => {
e.stopPropagation();
const handleOk = () => {
form.submit();
setIsModalVisible(false);
};
@@ -34,12 +33,12 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
setIsModalVisible(false);
setLoading(false);
};
const handleFinish = async ({ template, ...values }) => {
const handleFinish = async (values) => {
const { sendtype, ...restVals } = values;
setLoading(true);
await GenerateDocument(
{
name: TemplateList("job_special")[template].key,
name: TemplateList("job_special").folder_label_multiple.key,
variables: { id: jobId },
context: restVals,
},
@@ -49,7 +48,6 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
);
setLoading(false);
setIsModalVisible(false);
form.resetFields();
};
const content = (
@@ -60,28 +58,13 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
layout="vertical"
form={form}
>
<Form.Item required name="template">
<Radio.Group>
<Radio.Button value="parts_label_multiple">
{t("printcenter.jobs.parts_label_multiple")}
</Radio.Button>
<Radio.Button value="folder_label_multiple">
{t("printcenter.jobs.folder_label_multiple")}
</Radio.Button>
</Radio.Group>
</Form.Item>
<Form.Item
required
label={t("printcenter.jobs.labels.position")}
name="position"
>
<InputNumber min={1} precision={0} />
</Form.Item>
<Form.Item
required
label={t("printcenter.jobs.labels.count")}
name="count"
>
<Form.Item label={t("printcenter.jobs.labels.count")} name="count">
<InputNumber min={1} precision={0} max={99} />
</Form.Item>
<Button type="primary" loading={loading} onClick={handleOk}>

View File

@@ -20,7 +20,7 @@ const mapDispatchToProps = (dispatch) => ({});
export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const [search, setSearch] = useState("");
const { id: jobId, job } = printCenterModal.context;
const { id: jobId } = printCenterModal.context;
const tempList = TemplateList("job", {});
const { t } = useTranslation();
const JobsReportsList = Object.keys(tempList)
@@ -54,7 +54,7 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
extra={
<Space wrap>
<PrintCenterJobsLabels jobId={jobId} />
<Jobd3RdPartyModal jobId={jobId} job={job} />
<Jobd3RdPartyModal jobId={jobId} />
<Input.Search
onChange={(e) => setSearch(e.target.value)}
value={search}

View File

@@ -3,9 +3,8 @@ import {
EyeFilled,
DownloadOutlined,
PauseCircleOutlined,
BranchesOutlined,
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tooltip } from "antd";
import { Card, Col, Row, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@@ -64,11 +63,6 @@ export default function ProductionBoardCard(
{card.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{card.iouparent && (
<Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
<span style={{ fontWeight: "bolder" }}>
<Link
to={

View File

@@ -1,5 +1,5 @@
import { PauseCircleOutlined, BranchesOutlined } from "@ant-design/icons";
import { Space, Tooltip } from "antd";
import { PauseCircleOutlined } from "@ant-design/icons";
import { Space } from "antd";
import i18n from "i18next";
import moment from "moment";
import { Link } from "react-router-dom";
@@ -61,11 +61,6 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && (
<Tooltip title={i18n.t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} />
</Tooltip>
)}
</Space>
</Link>
),

View File

@@ -238,8 +238,10 @@ export function ProductionListTable({
sticky
pagination={false}
size="small"
{...(Production_List_Status_Colors.treatment === "on" && {
onRow: (record, index) => {
className="production-list-table"
onRow={
Production_List_Status_Colors.treatment === "on" &&
((record, index) => {
if (!bodyshop.md_ro_statuses.production_colors) return null;
const color = bodyshop.md_ro_statuses.production_colors.find(
@@ -253,8 +255,8 @@ export function ProductionListTable({
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
},
};
},
})}
})
}
components={{
header: {
cell: ResizeableTitle,

View File

@@ -39,12 +39,7 @@ export function ScheduleCalendarHeaderComponent({
const ATSToday = useMemo(() => {
if (!events) return [];
return _.groupBy(
events.filter(
(e) =>
!e.vacation &&
e.isintake &&
moment(date).isSame(moment(e.start), "day")
),
events.filter((e) => moment(date).isSame(moment(e.start), "day")),
"job.alt_transport"
);
}, [events, date]);

View File

@@ -1,8 +1,6 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Checkbox, Col, PageHeader, Row, Space } from "antd";
import { t } from "i18next";
import React, { useMemo } from "react";
import useLocalStorage from "../../utils/useLocalStorage";
import { Button, Card, Col, PageHeader, Row, Space } from "antd";
import React from "react";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
@@ -10,23 +8,6 @@ import ScheduleProductionList from "../schedule-production-list/schedule-product
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
export default function ScheduleCalendarComponent({ data, refetch }) {
const [filter, setFilter] = useLocalStorage("filter_events", {
intake: true,
manual: true,
employeevacation: true,
});
const filteredData = useMemo(() => {
return data.filter(
(d) =>
d.block ||
(filter.intake && d.isintake) ||
(filter.manual && !d.isintake && d.block === false) ||
(d.__typename === "employee_vacation" &&
filter.employeevacation &&
!!d.employee)
);
}, [data, filter]);
return (
<Row gutter={[16, 16]}>
<ScheduleModal />
@@ -35,30 +16,6 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
<PageHeader
extra={
<Space wrap>
<Checkbox
checked={filter?.intake}
onChange={(e) => {
setFilter({ ...filter, intake: e.target.checked });
}}
>
{t("schedule.labels.intake")}
</Checkbox>
<Checkbox
checked={filter?.manual}
onChange={(e) => {
setFilter({ ...filter, manual: e.target.checked });
}}
>
{t("schedule.labels.manual")}
</Checkbox>
<Checkbox
checked={filter?.employeevacation}
onChange={(e) => {
setFilter({ ...filter, employeevacation: e.target.checked });
}}
>
{t("schedule.labels.employeevacation")}
</Checkbox>
<ScheduleVerifyIntegrity />
<Button
onClick={() => {
@@ -78,7 +35,7 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
<Col span={24}>
<Card>
<ScheduleCalendarWrapperComponent
data={filteredData}
data={data}
refetch={refetch}
style={{ height: "100rem" }}
/>

View File

@@ -1,42 +1,42 @@
import { useQuery } from "@apollo/client";
import { Button, Card, Input, Modal, Space, Table, Typography } from "antd";
import React, { useState } from "react";
import { Dropdown, Button, Table, Space, Card, Input } from "antd";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { QUERY_SCOREBOARD_PAGINATED } from "../../graphql/scoreboard.queries";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
import { SyncOutlined } from "@ant-design/icons";
import { DateFormatter } from "../../utils/DateFormatter";
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 }) {
const { t } = useTranslation();
const [state, setState] = useState({
visible: false,
search: "",
current: 1,
pageSize: 10,
});
const [searchText, setSearchText] = useState("");
const { loading, error, data, refetch } = useQuery(
QUERY_SCOREBOARD_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: !state.visible,
variables: {
search: state.search !== "" ? `%${state.search}%` : null,
offset: state.current ? (state.current - 1) * state.pageSize : 0,
limit: state.pageSize,
order: [
{
date: "desc",
},
],
},
}
);
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 = [
{
@@ -97,69 +97,35 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
},
];
const overlay = (
<Card
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
pagination={false}
columns={columns}
rowKey="id"
dataSource={jobs}
onClick={(e) => e.stopPropagation()}
/>
</Card>
);
return (
<>
<Modal
visible={state.visible}
destroyOnClose
width="80%"
cancelButtonProps={{ style: { display: "none" } }}
onCancel={() =>
setState((state) => ({
...state,
visible: false,
current: 1,
search: "",
}))
}
>
{error && (
<AlertComponent type="error" message={JSON.stringify(error)} />
)}
<Card
extra={
<Space align="middle" wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Typography.Title level={4}>
{t("general.labels.searchresults", { search: state.search })}
</Typography.Title>
<Input.Search
placeholder={t("jobs.fields.ro_number")}
allowClear
onSearch={(value) => {
setState((state) => ({ ...state, search: value }));
}}
//value={state.search}
enterButton
/>
</Space>
}
>
<Table
columns={columns}
rowKey="id"
dataSource={data ? data.scoreboard : []}
loading={loading}
onClick={(e) => e.stopPropagation()}
onChange={(tableArgs) =>
setState((state) => ({ ...state, ...tableArgs }))
}
pagination={{
position: "top",
pageSize: state.pageSize || 10,
current: state.current || 1,
total: data ? data.scoreboard_aggregate.aggregate.count : 0,
}}
/>
</Card>
</Modal>
<Button
onClick={() => setState((state) => ({ ...state, visible: true }))}
>
{t("scoreboard.labels.entries")}
</Button>
</>
<Dropdown trigger={["click"]} overlay={overlay}>
<Button>Jobs</Button>
</Dropdown>
);
}

View File

@@ -17,8 +17,6 @@ export default function ScoreboardRemoveButton({ scoreboardId }) {
setLoading(true);
const result = await deleteScoreboardEntry({
variables: { sbId: scoreboardId },
awaitRefetchQueries: true,
refetchQueries: ["QUERY_SCOREBOARD_PAGINATED"],
});
if (!!result.errors) {

View File

@@ -80,7 +80,6 @@ export default function ScoreboardTimeTickets() {
totalThisMonth: 0,
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
employees: {},
};
data.fixedperiod.forEach((ticket) => {
@@ -93,7 +92,6 @@ export default function ScoreboardTimeTickets() {
totalThisMonth: 0,
totalLastMonth: 0,
totalOverPeriod: 0,
actualTotalOverPeriod: 0,
};
}
@@ -184,9 +182,6 @@ export default function ScoreboardTimeTickets() {
ret.employees[ticket.employee.employee_number].totalOverPeriod =
ret.employees[ticket.employee.employee_number].totalOverPeriod +
ticket.productivehrs;
ret.employees[ticket.employee.employee_number].actualTotalOverPeriod =
ret.employees[ticket.employee.employee_number]
.actualTotalOverPeriod + (ticket.actualhrs || 0);
if (!totals.employees[ticket.employee.employee_number])
totals.employees[ticket.employee.employee_number] = {
@@ -224,7 +219,7 @@ export default function ScoreboardTimeTickets() {
roundObject(ret);
roundObject(totals);
roundObject(ret2);
console.log(ret);
return {
fixed: ret,
timeperiod: {

View File

@@ -19,6 +19,7 @@ export default connect(
export function ScoreboardTicketsStats({ data, bodyshop }) {
const { t } = useTranslation();
console.log(data);
const columns = [
{
title: t("employees.fields.employee_number"),
@@ -56,16 +57,6 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
key: "totalOverPeriod",
sorter: (a, b) => a.totalOverPeriod - b.totalOverPeriod,
},
{
title: t("scoreboard.labels.efficiencyoverperiod"),
dataIndex: "efficiencyoverperiod",
key: "efficiencyoverperiod",
render: (text, record) =>
`${(
(record.totalOverPeriod / (record.actualTotalOverPeriod || .1)) *
100
).toFixed(1)} %`,
},
];
const tableData = data
@@ -121,7 +112,6 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
<Col md={24} lg={20}>
<Table
columns={columns}
rowKey='employee_number'
dataSource={tableData}
id="employee_number"
scroll={{ y: "300px" }}

View File

@@ -43,10 +43,9 @@ export default function ShopEmployeeAddVacation({ employee }) {
});
} else {
notification["success"]({
message: t("employees.successes.vacationadded"),
message: t("employees.successes.added"),
});
}
form.resetFields();
setLoading(false);
setVisibility(false);
};

View File

@@ -1494,7 +1494,7 @@ export default function ShopInfoGeneral({ form }) {
}
const ReceivableCustomFieldSelect = (
<Select allowClear>
<Select>
<Select.Option value="v_vin">VIN</Select.Option>
<Select.Option value="clm_no">Claim No.</Select.Option>
<Select.Option value="ded_amt">Deductible Amount</Select.Option>

View File

@@ -16,7 +16,6 @@ import DataLabel from "../data-label/data-label.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { useTreatments } from "@splitsoftware/splitio-react";
const SelectorDiv = styled.div`
.ant-form-item .ant-select {
@@ -38,16 +37,7 @@ export default connect(
export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
const { t } = useTranslation();
const { Qb_Multi_Ar } = useTreatments(
["Qb_Multi_Ar"],
{},
bodyshop && bodyshop.imexshopid
);
const { DmsAp } = useTreatments(
["DmsAp"],
{},
bodyshop && bodyshop.imexshopid
);
const [costOptions, setCostOptions] = useState(
[
...((form.getFieldValue(["md_responsibility_centers", "costs"]) &&
@@ -148,15 +138,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Switch />
</Form.Item>
)}
{bodyshop.pbs_serialnumber && (
<Form.Item
label={t("bodyshop.fields.dms.disablebillwip")}
valuePropName="checked"
name={["pbs_configuration", "disablebillwip"]}
>
<Switch />
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
<Form.List name={["cdk_configuration", "payers"]}>
@@ -4172,121 +4153,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.federal_tax_itc")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "taxes", "federal_itc", "name"]}
>
<Input />
</Form.Item>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"md_responsibility_centers",
"taxes",
"federal_itc",
"accountnumber",
]}
>
<Input />
</Form.Item> */}
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"md_responsibility_centers",
"taxes",
"federal_itc",
"accountname",
]}
>
<Input />
</Form.Item> */}
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"md_responsibility_centers",
"taxes",
"federal_itc",
"accountdesc",
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"md_responsibility_centers",
"taxes",
"federal_itc",
"accountitem",
]}
>
<Input />
</Form.Item>
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
<Form.Item
label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"md_responsibility_centers",
"taxes",
"federal_itc",
"dms_acctnumber",
]}
>
<Input />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_rate")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "taxes", "federal_itc", "rate"]}
>
<InputNumber precision={2} />
</Form.Item>
</LayoutFormRow>
)}
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.state_tax")}
@@ -4545,71 +4411,68 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input />
</Form.Item> */}
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<LayoutFormRow header={t("bodyshop.fields.responsibilitycenters.ap")}>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ap")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "name"]}
>
<Input />
</Form.Item> */}
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountnumber"]}
>
<Input />
</Form.Item> */}
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountname"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountdesc"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.dms.dms_acctnumber")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "dms_acctnumber"]}
>
<Input />
</Form.Item>
</LayoutFormRow>
)}
{/* <LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.responsibilitycenters.ap")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "name"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountnumber"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountname"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountdesc"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_responsibility_centers", "ap", "accountitem"]}
>
<Input />
</Form.Item>
</LayoutFormRow> */}
<LayoutFormRow header={<div>Refund</div>}>
{/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.refund")}
@@ -4672,26 +4535,6 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input />
</Form.Item>
</LayoutFormRow>
{Qb_Multi_Ar.treatment === "on" && (
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
<Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"md_responsibility_centers",
"qb_multiple_payers",
"accountitem",
]}
>
<Input />
</Form.Item>
</LayoutFormRow>
)}
<Typography.Title level={4}>
{t("bodyshop.labels.responsibilitycenters.sales_tax_codes")}
</Typography.Title>

View File

@@ -27,7 +27,6 @@ export function TimeTicektShiftContainer({
technician,
currentUser,
isTechConsole,
checkIfAlreadyClocked,
}) {
const [form] = Form.useForm();
const [insertTimeTicket] = useMutation(INSERT_NEW_TIME_TICKET);
@@ -44,47 +43,36 @@ export function TimeTicektShiftContainer({
const handleFinish = async (values) => {
setLoading(true);
const theTime = moment((await axios.post("/utils/time")).data);
const alreadyClocked = await checkIfAlreadyClocked();
const result = await insertTimeTicket({
variables: {
timeTicketInput: [
{
bodyshopid: bodyshop.id,
employeeid: isTechConsole ? technician.id : employeeId,
cost_center: "timetickets.labels.shift",
clockon: theTime,
date: theTime,
memo: values.memo,
},
],
},
awaitRefetchQueries: true,
refetchQueries: ["QUERY_ACTIVE_SHIFT_TIME_TICKETS"],
});
if (alreadyClocked) {
//Show the error.
if (!!result.errors) {
notification["error"]({
message: t("timetickets.errors.shiftalreadyclockedon"),
message: t("timetickets.errors.clockingin", {
message: JSON.stringify(result.errors),
}),
});
} else {
const theTime = moment((await axios.post("/utils/time")).data);
const result = await insertTimeTicket({
variables: {
timeTicketInput: [
{
bodyshopid: bodyshop.id,
employeeid: isTechConsole ? technician.id : employeeId,
cost_center: "timetickets.labels.shift",
clockon: theTime,
date: theTime,
memo: values.memo,
},
],
},
awaitRefetchQueries: true,
refetchQueries: ["QUERY_ACTIVE_SHIFT_TIME_TICKETS"],
notification["success"]({
message: t("timetickets.successes.clockedin"),
});
if (!!result.errors) {
notification["error"]({
message: t("timetickets.errors.clockingin", {
message: JSON.stringify(result.errors),
}),
});
} else {
notification["success"]({
message: t("timetickets.successes.clockedin"),
});
}
}
setLoading(false);
};

View File

@@ -64,12 +64,6 @@ export function TimeTicketShiftContainer({
</div>
);
const checkIfAlreadyClocked = async () => {
const { data } = await refetch();
return data.timetickets.length > 0;
};
return (
<div>
{data.timetickets.length > 0 ? (
@@ -78,10 +72,7 @@ export function TimeTicketShiftContainer({
refetch={refetch}
/>
) : (
<TimeTicketShiftFormContainer
isTechConsole={isTechConsole}
checkIfAlreadyClocked={checkIfAlreadyClocked}
/>
<TimeTicketShiftFormContainer isTechConsole={isTechConsole} />
)}
</div>
);

View File

@@ -2,7 +2,7 @@ import React from "react";
export default function VehicleVinDisplay({ children }) {
if (!children) return null;
console.log(children);
if (typeof children !== "string" || children.length !== 17) return children;
const vin = children.trim();

View File

@@ -1,6 +1,5 @@
import { DeleteFilled } from "@ant-design/icons";
import { useApolloClient } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
Divider,
@@ -21,23 +20,7 @@ import PhoneFormItem, {
} from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorsPhonebookAdd from "../vendors-phonebook-add/vendors-phonebook-add.component";
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
)(VendorsFormComponent);
export function VendorsFormComponent({
bodyshop,
export default function VendorsFormComponent({
form,
formLoading,
handleDelete,
@@ -46,12 +29,6 @@ export function VendorsFormComponent({
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { DmsAp } = useTreatments(
["DmsAp"],
{},
bodyshop && bodyshop.imexshopid
);
const { getFieldValue } = form;
return (
<div>
@@ -207,12 +184,6 @@ export function VendorsFormComponent({
// </Form.Item>
}
</LayoutFormRow>
{DmsAp.treatment === "on" && (
<Form.Item label={t("vendors.fields.dmsid")} name="dmsid">
<Input />
</Form.Item>
)}
<Divider align="left">{t("vendors.labels.preferredmakes")}</Divider>
<Form.List name="favorite">
{(fields, { add, remove }) => {

View File

@@ -53,6 +53,7 @@ export const requestForToken = () => {
})
.then((currentToken) => {
if (currentToken) {
console.log("current token for client: ", currentToken);
window.sessionStorage.setItem("fcmtoken", currentToken);
// Perform any other necessary action with the token
} else {

View File

@@ -8,13 +8,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
$endd: date!
) {
employee_vacation(
where: {
_or: [
{ start: { _gte: $startd } }
{ end: { _lte: $endd } }
{ _and: [{ start: { _lte: $startd } }, { end: { _gte: $endd } }] }
]
}
where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] }
) {
id
start
@@ -386,8 +380,8 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
}
`;
export const MARK_APPOINTMENT_ARRIVED = gql`
mutation MARK_APPOINTMENT_ARRIVED($appointmentId: uuid!) {
export const MARK_LATEST_APPOINTMENT_AS_ARRIVED = gql`
mutation MARK_LATEST_APPOINTMENT_AS_ARRIVED($appointmentId: uuid!) {
update_appointments(
where: { id: { _eq: $appointmentId } }
_set: { arrived: true }
@@ -400,21 +394,3 @@ export const MARK_APPOINTMENT_ARRIVED = gql`
}
}
`;
export const MARK_LATEST_APPOINTMENT_ARRIVED = gql`
mutation MARK_LATEST_APPOINTMENT_ARRIVED($jobId: uuid!) {
update_appointments(
where: {
jobid: { _eq: $jobId }
canceled: { _eq: false }
isintake: { _eq: true }
}
_set: { arrived: true }
) {
affected_rows
returning {
id
arrived
}
}
}
`;

View File

@@ -13,21 +13,6 @@ export const QUERY_AUDIT_TRAIL = gql`
created
bodyshopid
}
email_audit_trail(
where: { jobid: { _eq: $jobid } }
order_by: { created_at: desc }
) {
cc
contents
created_at
id
jobid
noteid
subject
to
useremail
status
}
}
`;

View File

@@ -23,7 +23,6 @@ export const GET_ALL_JOBLINES_BY_PK = gql`
notes
location
tax_part
manual_line
}
}
`;
@@ -50,7 +49,6 @@ export const GET_LINE_TICKET_BY_PK = gql`
lbr_amt
op_code_desc
convertedtolbr
convertedtolbr_data
}
timetickets(where: { jobid: { _eq: $id } }) {
actualhrs
@@ -181,7 +179,6 @@ export const UPDATE_JOB_LINE = gql`
status
removed
convertedtolbr
convertedtolbr_data
}
}
}
@@ -198,7 +195,6 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
line_desc
part_type
oem_partno
alt_partno
db_price
act_price
part_qty
@@ -208,7 +204,6 @@ export const GET_JOB_LINES_TO_ENTER_BILL = gql`
lbr_op
lbr_amt
op_code_desc
alt_partno
}
jobs_by_pk(id: $id) {
id

View File

@@ -6,7 +6,6 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
where: { status: { _in: $statuses } }
order_by: { created_at: desc }
) {
iouparent
ownr_fn
ownr_ln
ownr_co_nm
@@ -41,8 +40,6 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
updated_at
ded_amt
suspended
est_ct_fn
est_ct_ln
}
}
`;
@@ -110,7 +107,6 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
query QUERY_EXACT_JOB_IN_PRODUCTION($id: uuid!) {
jobs(where: { id: { _eq: $id } }) {
id
iouparent
status
ro_number
comment
@@ -189,7 +185,6 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
query QUERY_EXACT_JOBS_IN_PRODUCTION($ids: [uuid!]!) {
jobs(where: { id: { _in: $ids } }) {
id
iouparent
status
ro_number
comment
@@ -272,7 +267,6 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
comment
status
category
iouparent
ro_number
ownr_fn
ownr_ln
@@ -613,7 +607,6 @@ export const GET_JOB_BY_PK = gql`
materials
auto_add_ats
rate_ats
iouparent
owner {
id
ownr_fn
@@ -628,7 +621,6 @@ export const GET_JOB_BY_PK = gql`
ownr_ctry
ownr_ph1
ownr_ph2
tax_number
}
labor_rate_desc
rate_la1
@@ -692,7 +684,6 @@ export const GET_JOB_BY_PK = gql`
line_ref
part_type
oem_partno
alt_partno
db_price
act_price
part_qty
@@ -855,7 +846,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
count
status
}
joblines(where: { removed: { _eq: false } }) {
joblines {
id
mod_lbr_ty
mod_lb_hrs
@@ -864,7 +855,6 @@ export const QUERY_JOB_CARD_DETAILS = gql`
id
allow_text_message
preferred_contact
tax_number
}
vehicleid
v_model_yr
@@ -1895,7 +1885,6 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
actual_in
kmin
kmout
qb_multiple_payers
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
id
removed
@@ -1918,7 +1907,6 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
profitcenter_part
prt_dsmk_p
convertedtolbr
convertedtolbr_data
}
}
}
@@ -2120,22 +2108,6 @@ export const DELETE_RELATED_RO = gql`
`;
export const GET_JOB_LINE_ORDERS = gql`
query GET_JOB_LINE_ORDERS($joblineid: uuid!) {
billlines(where: { joblineid: { _eq: $joblineid } }) {
actual_cost
actual_price
billid
quantity
bill {
id
invoice_number
date
vendorid
vendor {
id
name
}
}
}
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
id
act_price

View File

@@ -67,7 +67,6 @@ export const QUERY_OWNER_BY_ID = gql`
ownr_zip
preferred_contact
note
tax_number
jobs {
id
ro_number

View File

@@ -88,41 +88,3 @@ export const GET_BLOCKED_DAYS = gql`
}
}
`;
export const QUERY_SCOREBOARD_PAGINATED = gql`
query QUERY_SCOREBOARD_PAGINATED(
$search: String
$offset: Int
$limit: Int
$order: [scoreboard_order_by!]
) {
scoreboard(
where: { job: { ro_number: { _ilike: $search } } }
offset: $offset
limit: $limit
order_by: $order
) {
id
jobid
job {
id
ro_number
invoice_date
v_make_desc
v_model_desc
v_model_yr
ownr_fn
ownr_ln
ownr_co_nm
}
date
bodyhrs
painthrs
}
scoreboard_aggregate(where: { job: { ro_number: { _ilike: $search } } }) {
aggregate {
count(distinct: true)
}
}
}
`;

View File

@@ -79,7 +79,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
date
id
rate
actualhrs
productivehrs
memo
jobid

Some files were not shown because too many files have changed in this diff Show More