Merged in release/2022-02-04 (pull request #366)

Release/2022 02 04
This commit is contained in:
Patrick Fic
2022-02-05 01:35:28 +00:00
56 changed files with 255 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -40261,6 +40261,53 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>validation</name>
<children>
<concept_node>
<name>clockoffmustbeafterclockon</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>clockoffwithoutclockon</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>

View File

@@ -8,7 +8,7 @@ import {
Space,
Switch,
Table,
Tooltip,
Tooltip
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -334,6 +334,19 @@ export function BillEnterModalLinesComponent({
additional: (record, index) => (
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
{() => {
const price = getFieldValue([
"billlines",
record.name,
"actual_price",
]);
const adjustmentRate = getFieldValue([
"billlines",
record.name,
"lbr_adjustment",
"rate",
]);
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
return (
<div>
@@ -406,6 +419,9 @@ export function BillEnterModalLinesComponent({
>
<InputNumber precision={2} min={0.01} />
</Form.Item>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
</div>
);
return <></>;

View File

@@ -38,6 +38,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion:null,
status: bodyshop.md_ro_statuses.default_imported,
},
},

View File

@@ -6,9 +6,11 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setPartnerVersion } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -19,7 +21,7 @@ export default connect(
mapDispatchToProps
)(PartnerPingComponent);
export function PartnerPingComponent({ setPartnerVersion }) {
export function PartnerPingComponent({ bodyshop, setPartnerVersion }) {
const { t } = useTranslation();
useEffect(() => {
@@ -29,16 +31,25 @@ export function PartnerPingComponent({ setPartnerVersion }) {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
const { appver, qbpath } = PartnerResponse.data;
if (!bodyshop) return;
setPartnerVersion(appver);
console.log({ appver, qbpath });
if (!qbpath) {
if (
!qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
)
) {
notification["error"]({
title: "",
message: t("general.messages.noacctfilepath"),
});
}
} catch (error) {
console.log(error);
notification["error"]({
title: "",
message: t("general.messages.partnernotrunning"),
@@ -48,7 +59,7 @@ export function PartnerPingComponent({ setPartnerVersion }) {
// Execute the created function directly
checkPartnerStatus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [bodyshop]);
return <></>;
}

View File

@@ -30,6 +30,8 @@ import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios";
import { useTreatments } from "@splitsoftware/splitio-react";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -57,6 +59,11 @@ export function PartsOrderModalContainer({
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { visible, context, actions } = partsOrderModal;
const {
jobId,
@@ -230,11 +237,22 @@ export function PartsOrderModalContainer({
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
let po;
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
if (OEConnection_PriceChange.treatment === "on") {
//Set the flag to include the override.
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
po.parts_order_lines.forEach((pol) => {
pol.priceChange = true;
});
}
console.log(partsOrder.data.parts_orders_by_pk);
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
partsOrder.data.parts_orders_by_pk,
po || partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,

View File

@@ -208,7 +208,7 @@ export function TimeTicketModalComponent({
>
<InputNumber min={0} precision={1} />
</Form.Item>
{isEdit && (
{
<>
<Form.Item label={t("timetickets.fields.clockon")} name="clockon">
<FormDateTimePicker
@@ -221,7 +221,29 @@ export function TimeTicketModalComponent({
}
/>
</Form.Item>
<Form.Item label={t("timetickets.fields.clockoff")} name="clockoff">
<Form.Item
label={t("timetickets.fields.clockoff")}
name="clockoff"
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
const clockon = getFieldValue("clockon");
if (!value) return Promise.resolve();
if (!clockon && value)
return Promise.reject(
t("timetickets.validation.clockoffwithoutclockon")
);
if (!value.isSameOrAfter(clockon))
return Promise.reject(
t("timetickets.validation.clockoffmustbeafterclockon")
);
return Promise.resolve();
},
}),
]}
>
<FormDateTimePicker
disabled={
!HasRbacAccess({
@@ -233,7 +255,7 @@ export function TimeTicketModalComponent({
/>
</Form.Item>
</>
)}
}
<Form.Item label={t("timetickets.fields.memo")} name="memo">
<MemoInput />

View File

@@ -2406,6 +2406,10 @@
"clockedout": "Clocked out successfully.",
"created": "Time ticket entered successfully.",
"deleted": "Time ticket deleted successfully."
},
"validation": {
"clockoffmustbeafterclockon": "Clock off time must be the same or after clock in time.",
"clockoffwithoutclockon": "Clock off time cannot be set without a clock in time."
}
},
"titles": {

View File

@@ -2406,6 +2406,10 @@
"clockedout": "",
"created": "",
"deleted": ""
},
"validation": {
"clockoffmustbeafterclockon": "",
"clockoffwithoutclockon": ""
}
},
"titles": {

View File

@@ -2406,6 +2406,10 @@
"clockedout": "",
"created": "",
"deleted": ""
},
"validation": {
"clockoffmustbeafterclockon": "",
"clockoffwithoutclockon": ""
}
},
"titles": {

View File

@@ -815,6 +815,7 @@
- email
- enforce_class
- enforce_referral
- entegral_id
- features
- federal_tax_id
- id

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_bodyshopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_bodyshopid" on
"public"."appointments" using btree ("bodyshopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_jobid" on
"public"."appointments" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_start";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_start" on
"public"."appointments" using btree ("start");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_end";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_end" on
"public"."appointments" using btree ("end");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "associations_bodyshopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "associations_bodyshopid" on
"public"."associations" using btree ("shopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "associations_useremail";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "associations_useremail" on
"public"."associations" using btree ("useremail");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "invoicelines_billid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "invoicelines_billid" on
"public"."billlines" using btree ("billid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "invoicelines_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "invoicelines_jobid" on
"public"."billlines" using btree ("joblineid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "bills_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "bills_jobid" on
"public"."bills" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "conversations_shopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "conversations_shopid" on
"public"."conversations" using btree ("bodyshopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "documents_shopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "documents_shopid" on
"public"."documents" using btree ("bodyshopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "documents_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "documents_jobid" on
"public"."documents" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "documents_billid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "documents_billid" on
"public"."documents" using btree ("billid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "jobline_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "jobline_jobid" on
"public"."joblines" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "jobs_updatedat";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "jobs_updatedat" on
"public"."jobs" using btree ("updated_at");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "jobs_bodyshopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "jobs_bodyshopid" on
"public"."jobs" using btree ("shopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "messages_conversationid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "messages_conversationid" on
"public"."messages" using btree ("conversationid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "timetickets_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "timetickets_jobid" on
"public"."timetickets" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "parts_orders_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_orders_jobid" on
"public"."parts_orders" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "parts_order_lines_poid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_order_lines_poid" on
"public"."parts_order_lines" using btree ("orderid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "parts_order_lines_joblineid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "parts_order_lines_joblineid" on
"public"."parts_order_lines" using btree ("job_line_id");

View File

@@ -9,6 +9,7 @@ exports.default = function ({
qbo = false,
items,
taxCodes,
classes,
}) {
const InvoiceLineAdd = [];
const responsibilityCenters = bodyshop.md_responsibility_centers;
@@ -95,6 +96,9 @@ exports.default = function ({
DetailType: "SalesItemLineDetail",
Amount: DineroAmount,
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[account.accountitem],
},
@@ -161,6 +165,9 @@ exports.default = function ({
DetailType: "SalesItemLineDetail",
Amount: DineroAmount,
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[account.accountitem],
},
@@ -227,6 +234,9 @@ exports.default = function ({
ItemRef: {
value: items[mapaAccount.accountitem],
},
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
TaxCodeRef: {
value: QboTaxId,
},
@@ -290,6 +300,9 @@ exports.default = function ({
ItemRef: {
value: items[mashAccount.accountitem],
},
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
TaxCodeRef: {
value: QboTaxId,
},
@@ -367,6 +380,9 @@ exports.default = function ({
amount: Math.round((jobs_by_pk.towing_payable || 0) * 100),
}).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[account.accountitem],
},
@@ -415,6 +431,9 @@ exports.default = function ({
amount: Math.round((jobs_by_pk.storage_payable || 0) * 100),
}).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[account.accountitem],
},
@@ -466,6 +485,9 @@ exports.default = function ({
amount: Math.round((jobs_by_pk.adjustment_bottom_line || 0) * 100),
}).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items[account.accountitem],
},
@@ -554,6 +576,9 @@ exports.default = function ({
DineroQbFormat
),
SalesItemLineDetail: {
...(jobs_by_pk.class
? { ClassRef: { value: classes[jobs_by_pk.class] } }
: {}),
ItemRef: {
value: items["PVRT"],
},

View File

@@ -181,11 +181,9 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
DocNumber: bill.invoice_number,
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
PrivateNote: `RO ${bill.job.ro_number || ""} OWNER ${
bill.job.ownr_fn || ""
} ${bill.job.ownr_ln || ""} ${bill.job.ownr_co_nm || ""}`,
PrivateNote: `RO ${bill.job.ro_number || ""}`,
Line: bill.billlines.map((il) =>
generateBillLine(
il,
@@ -249,12 +247,11 @@ const generateBillLine = (
) => {
const account = costCenters.find((c) => c.name === billLine.cost_center);
console.log(account.accountname, accounts[account.accountname]);
return {
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}),
...(jobClass ? { ClassRef: { value: classes[jobClass] } } : {}),
TaxCodeRef: {
value:
taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)],
@@ -324,7 +321,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
classes.json.QueryResponse &&
classes.json.QueryResponse.Class &&
classes.json.QueryResponse.Class.forEach((t) => {
accountMapping[t.Name] = t.Id;
classMapping[t.Name] = t.Id;
});
return {

View File

@@ -356,7 +356,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
exports.InsertJob = InsertJob;
async function QueryMetaData(oauthClient, qbo_realmId, req) {
const items = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Item`),
url: urlBuilder(qbo_realmId, "query", `select * From Item where active=true maxresults 1000`),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -402,7 +402,7 @@ async function QueryMetaData(oauthClient, qbo_realmId, req) {
classes.json.QueryResponse &&
classes.json.QueryResponse.Class &&
classes.json.QueryResponse.Class.forEach((t) => {
itemMapping[t.Name] = t.Id;
classMapping[t.Name] = t.Id;
});
return {
@@ -431,13 +431,19 @@ async function InsertInvoice(
qbo: true,
items,
taxCodes,
classes
});
const invoiceObj = {
Line: InvoiceLineAdd,
TxnDate: moment(job.date_invoiced).format("YYYY-MM-DD"),
DocNumber: job.ro_number,
...(job.class ? { ClassRef: { Id: classes[job.class] } } : {}),
...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
CustomerMemo: {
value: `${job.clm_no ? `Claim No: ${job.clm_no}` : ``}${
job.po_number ? `PO No: ${job.po_number}` : ``
}`.trim(),
},
CustomerRef: {
value: parentTierRef.Id,
},
@@ -447,6 +453,15 @@ async function InsertInvoice(
...(bodyshop.accountingconfig.emaillater && job.ownr_ea
? { EmailStatus: "NeedToSend" }
: {}),
BillAddr: {
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${
job.ownr_zip || ""
}`.trim(),
Line2: job.ownr_addr1 || "",
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`,
},
};
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {

View File

@@ -566,6 +566,13 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
status
est_ct_fn
est_ct_ln
ownr_fn
ownr_ln
ownr_co_nm
ownr_addr1
ownr_city
ownr_st
ownr_ph1
ownr_zip
referral_source
loss_type