+
+
+
+
+
+
+ {errors.length > 0 && (
+
+ {t("general.labels.errors")}
+
+ {errors.map((error, idx) => (
+ - {error}
+ ))}
+
+
+ )}
+
);
}
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
new file mode 100644
index 000000000..274b35621
--- /dev/null
+++ b/client/src/components/job-reconciliation-totals/job-reconciliation-totals.utility.js
@@ -0,0 +1,106 @@
+import i18next from "i18next";
+import _ from "lodash";
+export const reconcileByAssocLine = (
+ jobLines,
+ jobLineState,
+ billLines,
+ billLineState,
+ setErrors
+) => {
+ const [selectedBillLines, setSelectedBillLines] = billLineState;
+ const [selectedJobLines, setSelectedJobLines] = jobLineState;
+
+ const allJoblinesFromBills = billLines
+ .filter((bl) => !!bl.joblineid)
+ .map((bl) => bl.joblineid);
+
+ const duplicatedJobLinesbyInvoiceId = _.filter(
+ allJoblinesFromBills,
+ (val, i, iteratee) => _.includes(iteratee, val, i + 1)
+ );
+
+ if (duplicatedJobLinesbyInvoiceId.length > 0)
+ setErrors((errors) => [
+ ...errors,
+ ..._.uniqBy(duplicatedJobLinesbyInvoiceId).map((dupedId) =>
+ i18next.t("jobs.labels.reconciliation.multiplebilllines", {
+ line_desc: jobLines.find((j) => j.id === dupedId).line_desc,
+ })
+ ),
+ ]);
+
+ setSelectedBillLines(
+ _.union(
+ selectedBillLines,
+ billLines.filter((bl) => !!bl.joblineid).map((bl) => bl.id)
+ )
+ );
+
+ setSelectedJobLines(
+ _.union(selectedJobLines, _.uniqBy(allJoblinesFromBills))
+ );
+};
+
+export const reconcileByPrice = (
+ jobLines,
+ jobLineState,
+ billLines,
+ billLineState,
+ setErrors
+) => {
+ const [selectedBillLines, setSelectedBillLines] = billLineState;
+ const [selectedJobLines, setSelectedJobLines] = jobLineState;
+
+ const allActPricesFromJobs = jobLines.map((jl) => jl.act_price);
+ const duplicateActPrices = _.filter(
+ allActPricesFromJobs,
+ (val, i, iteratee) => _.includes(iteratee, val, i + 1)
+ );
+
+ if (duplicateActPrices.length > 0)
+ setErrors((errors) => [
+ ...errors,
+ ..._.uniqBy(duplicateActPrices).map((dupeAp) =>
+ i18next.t("jobs.labels.reconciliation.multipleactprices", {
+ act_price: dupeAp,
+ })
+ ),
+ ]);
+
+ const foundJobLines = [];
+ var foundBillLines = [];
+ const actPricesWithMoreThan1MatchingBill = [];
+
+ jobLines.forEach((jl) => {
+ const matchingBillLineIds = billLines
+ .filter((bl) => bl.actual_price === jl.act_price)
+ .map((bl) => bl.id);
+
+ if (matchingBillLineIds.length > 1) {
+ actPricesWithMoreThan1MatchingBill.push(jl.act_price);
+ }
+
+ foundBillLines = [...foundBillLines, ...matchingBillLineIds];
+ if (matchingBillLineIds.length > 0) {
+ foundJobLines.push(jl.id);
+ }
+ });
+
+ setErrors((errors) => [
+ ...errors,
+ ..._.uniqBy(duplicateActPrices).map((dupeAp) =>
+ i18next.t("jobs.labels.reconciliation.multipleactprices", {
+ act_price: dupeAp,
+ })
+ ),
+ ..._.uniqBy(actPricesWithMoreThan1MatchingBill).map((act_price) =>
+ i18next.t("jobs.labels.reconciliation.multiplebillsforactprice", {
+ act_price: act_price,
+ })
+ ),
+ ]);
+
+ setSelectedBillLines(_.union(selectedBillLines, _.uniqBy(foundBillLines)));
+
+ setSelectedJobLines(_.union(selectedJobLines, _.uniqBy(foundJobLines)));
+};
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 047c2eff8..528352609 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -96,6 +96,7 @@
},
"labels": {
"entered": "Entered",
+ "from": "From",
"other": "--Not On Estimate--",
"reconciled": "Reconciled!",
"unreconciled": "Unreconciled"
@@ -1048,6 +1049,15 @@
"partstotal": "Parts Total",
"rates": "Rates",
"rates_subtotal": "Rates Subtotal",
+ "reconciliation": {
+ "billlinestotal": "Bill Lines Total",
+ "byassoc": "By Line Association",
+ "byprice": "By Price",
+ "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."
+ },
"reconciliationheader": "Parts & Sublet Reconciliation",
"sale_labor": "Sales - Labor",
"sale_parts": "Sales - Parts",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 4a5977a2c..1af54bb26 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -96,6 +96,7 @@
},
"labels": {
"entered": "",
+ "from": "",
"other": "",
"reconciled": "",
"unreconciled": ""
@@ -1048,6 +1049,15 @@
"partstotal": "",
"rates": "Tarifas",
"rates_subtotal": "",
+ "reconciliation": {
+ "billlinestotal": "",
+ "byassoc": "",
+ "byprice": "",
+ "joblinestotal": "",
+ "multipleactprices": "",
+ "multiplebilllines": "",
+ "multiplebillsforactprice": ""
+ },
"reconciliationheader": "",
"sale_labor": "",
"sale_parts": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index eed4a568a..c32b0f16e 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -96,6 +96,7 @@
},
"labels": {
"entered": "",
+ "from": "",
"other": "",
"reconciled": "",
"unreconciled": ""
@@ -1048,6 +1049,15 @@
"partstotal": "",
"rates": "Les taux",
"rates_subtotal": "",
+ "reconciliation": {
+ "billlinestotal": "",
+ "byassoc": "",
+ "byprice": "",
+ "joblinestotal": "",
+ "multipleactprices": "",
+ "multiplebilllines": "",
+ "multiplebillsforactprice": ""
+ },
"reconciliationheader": "",
"sale_labor": "",
"sale_parts": "",