diff --git a/client/.env.development2 b/client/.env.development2
new file mode 100644
index 000000000..96ee2121d
--- /dev/null
+++ b/client/.env.development2
@@ -0,0 +1,13 @@
+FAST_REFRESH=false
+REACT_APP_GRAPHQL_ENDPOINT=https://bodyshop-dev-db.herokuapp.com/v1/graphql
+REACT_APP_GRAPHQL_ENDPOINT_WS=wss://bodyshop-dev-db.herokuapp.com/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/bodyshop
+REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
+REACT_APP_CLOUDINARY_API_KEY=473322739956866
+REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,f_auto,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://reports.bodyshop.app
\ No newline at end of file
diff --git a/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx b/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx
index cd45c8722..3850cf359 100644
--- a/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx
+++ b/client/src/components/job-reconciliation-modal/job-reconciliation.modal.container.jsx
@@ -1,11 +1,15 @@
+import { useQuery } from "@apollo/client";
import { Modal } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
+import { GET_JOB_RECONCILIATION_BY_PK } from "../../graphql/jobs.queries";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectReconciliation } from "../../redux/modals/modals.selectors";
import JobReconciliationModalComponent from "./job-reconciliation-modal.component";
+import LoadingSpinner from "../loading-spinner/loading-spinner.component";
+import AlertComponent from "../alert/alert.component";
const mapStateToProps = createStructuredSelector({
reconciliationModal: selectReconciliation,
@@ -20,7 +24,12 @@ function JobReconciliationModalContainer({
}) {
const { t } = useTranslation();
const { context, visible } = reconciliationModal;
- const { job, bills } = context;
+ const { job } = context;
+
+ const { loading, error, data } = useQuery(GET_JOB_RECONCILIATION_BY_PK, {
+ variables: { id: job && job.id },
+ skip: !(job && job.id) || !visible,
+ });
const handleCancel = () => {
toggleModalVisible();
@@ -37,7 +46,15 @@ function JobReconciliationModalContainer({
cancelButtonProps={{ display: "none" }}
destroyOnClose
>
-
+
+ {error && }
+ {data && (
+
+ )}
+
);
}
diff --git a/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx b/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx
index 089dfe1d4..4c35fdd94 100644
--- a/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx
+++ b/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.component.jsx
@@ -3,6 +3,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
+import "./job-reconciliation-parts-table.styles.scss";
export default function JobReconcilitionPartsTable({
jobLineState,
@@ -114,7 +115,11 @@ export default function JobReconcilitionPartsTable({
onChange: handleOnRowClick,
selectedRowKeys: selectedLines,
}}
+ rowClassName={(record) => record.removed && "text-strikethrough"}
/>
+
+ {t("jobs.labels.reconciliation.removedpartsstrikethrough")}
+
);
}
diff --git a/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.styles.scss b/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.styles.scss
new file mode 100644
index 000000000..fffdb8790
--- /dev/null
+++ b/client/src/components/job-reconciliation-parts-table/job-reconciliation-parts-table.styles.scss
@@ -0,0 +1,3 @@
+.text-strikethrough {
+ text-decoration: line-through;
+}
diff --git a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx
index 4f3474750..11330c5b9 100644
--- a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx
+++ b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.component.jsx
@@ -39,7 +39,9 @@ export default function JobReconciliationTotals({
return acc.add(
Dinero({
amount: Math.round((val.actual_price || 0) * 100),
- }).multiply(val.quantity || 1)
+ })
+ .multiply(val.quantity || 1)
+ .multiply(val.bill.is_credit_memo ? -1 : 1)
);
}, Dinero()),
};
@@ -97,6 +99,7 @@ export default function JobReconciliationTotals({
onClick={() => {
jobLineState[1]([]);
billLineState[1]([]);
+ setErrors([]);
}}
>
{t("jobs.labels.reconciliation.clear")}
diff --git a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
index e679b1102..cb4b9fc82 100644
--- a/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
+++ b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
@@ -23,11 +23,6 @@ export const reconcileByAssocLine = (
setErrors((errors) => [
...errors,
..._.uniqBy(duplicatedJobLinesbyInvoiceId).map((dupedId) => {
- console.log(
- "dupedId",
- dupedId,
- billLines.find((b) => b.id === dupedId)
- );
return i18next.t("jobs.labels.reconciliation.multiplebilllines", {
line_desc: jobLines.find((j) => j.id === dupedId)?.line_desc,
});
diff --git a/client/src/graphql/jobs.queries.js b/client/src/graphql/jobs.queries.js
index f7b6b8730..68d7ae507 100644
--- a/client/src/graphql/jobs.queries.js
+++ b/client/src/graphql/jobs.queries.js
@@ -580,7 +580,66 @@ export const GET_JOB_BY_PK = gql`
}
}
`;
-
+export const GET_JOB_RECONCILIATION_BY_PK = gql`
+ query GET_JOB_RECONCILIATION_BY_PK($id: uuid!) {
+ bills(where: { jobid: { _eq: $id } }) {
+ id
+ vendorid
+ vendor {
+ id
+ name
+ }
+ total
+ invoice_number
+ date
+ federal_tax_rate
+ state_tax_rate
+ local_tax_rate
+ is_credit_memo
+ isinhouse
+ exported
+ billlines {
+ actual_price
+ quantity
+ actual_cost
+ cost_center
+ id
+ joblineid
+ line_desc
+ applicable_taxes
+ deductedfromlbr
+ }
+ }
+ jobs_by_pk(id: $id) {
+ id
+ joblines(order_by: { line_no: asc }) {
+ id
+ removed
+ line_no
+ unq_seq
+ line_ind
+ line_desc
+ part_type
+ oem_partno
+ db_price
+ act_price
+ part_qty
+ mod_lbr_ty
+ db_hrs
+ mod_lb_hrs
+ lbr_op
+ lbr_amt
+ op_code_desc
+ status
+ notes
+ location
+ tax_part
+ db_ref
+ manual_line
+ }
+ }
+ }
+`;
export const QUERY_JOB_CARD_DETAILS = gql`
query QUERY_JOB_CARD_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 92313b507..eba3ba4f0 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -1229,7 +1229,8 @@
"joblinestotal": "Job Lines Total",
"multipleactprices": "${{act_price}} is the price for multiple job lines.",
"multiplebilllines": "{{line_desc}} has 2 or more bill lines associated to it.",
- "multiplebillsforactprice": "Found more than 1 bill matching ${{act_price}} retail price."
+ "multiplebillsforactprice": "Found more than 1 bill matching ${{act_price}} retail price.",
+ "removedpartsstrikethrough": "Strike through lines represent parts that have been removed from the estimate. They are included for completeness of reconciliation."
},
"reconciliationheader": "Parts & Sublet Reconciliation",
"rosaletotal": "Total RO Sale",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index be2219678..0fe165865 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -1229,7 +1229,8 @@
"joblinestotal": "",
"multipleactprices": "",
"multiplebilllines": "",
- "multiplebillsforactprice": ""
+ "multiplebillsforactprice": "",
+ "removedpartsstrikethrough": ""
},
"reconciliationheader": "",
"rosaletotal": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index b70d37225..526459af3 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -1229,7 +1229,8 @@
"joblinestotal": "",
"multipleactprices": "",
"multiplebilllines": "",
- "multiplebillsforactprice": ""
+ "multiplebillsforactprice": "",
+ "removedpartsstrikethrough": ""
},
"reconciliationheader": "",
"rosaletotal": "",