Compare commits

...

13 Commits

Author SHA1 Message Date
Patrick Fic
74f791541f IO-256 Resolve no invoice on CM QBO export. 2021-10-29 13:48:17 -07:00
Patrick Fic
630fa23f6c Resolve uneeded import CI error. 2021-10-29 11:30:43 -07:00
Patrick Fic
a0922f2944 IO-1476 Add Hrs to schedule modal. 2021-10-29 11:18:17 -07:00
Patrick Fic
b8f001625b IO-1502 PST on Adjustments for QBO 2021-10-29 10:55:35 -07:00
Patrick Fic
53394efebf IO-1503 QBO Credit Memos 2021-10-28 09:56:59 -07:00
Patrick Fic
2c00e5ee79 IO-256 Resolve QBO Aposstroph Issues 2021-10-27 11:36:40 -07:00
Patrick Fic
34e220dcad IO-233 Update Regex for CDK. 2021-10-27 10:46:06 -07:00
Patrick Fic
67eb430ff9 Add missing query field to CDK. 2021-10-27 08:39:05 -07:00
Patrick Fic
331b2c517b Update CDK Country Inclusion. 2021-10-27 08:29:43 -07:00
Patrick Fic
05d9f20a66 Mark Other RO field as Read Only 2021-10-26 15:38:05 -07:00
Patrick Fic
1511b87959 IO-1487 MPI Totals Adjustments 2021-10-26 13:10:07 -07:00
Patrick Fic
9c3e4b7b83 IO-1489 Resolve payables memo for QBO. 2021-10-25 17:25:29 -07:00
Patrick Fic
b718f49071 IO-256 Resolve QBO Payable Vendor Insert. 2021-10-25 17:05:21 -07:00
14 changed files with 4307 additions and 23 deletions

1
.gitignore vendored
View File

@@ -112,3 +112,4 @@ firebase/.env
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
logs/oAuthClient-log.log

View File

@@ -135,7 +135,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
label={t("jobs.fields.referral_source_extra")}
name="referral_source_extra"
>
<Input />
<Input disabled={jobRO}/>
</Form.Item>
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
<Select disabled={jobRO} allowClear>

View File

@@ -5,9 +5,8 @@ import {
Input,
Row,
Select,
Space,
Switch,
Typography,
Space, Switch,
Typography
} from "antd";
import axios from "axios";
import moment from "moment";
@@ -75,6 +74,12 @@ export function ScheduleJobModalComponent({
return (
<Row gutter={[16, 16]}>
<Col span={12}>
<Space>
<Typography.Title level={3}>{lbrHrsData?.jobs_by_pk?.ro_number}</Typography.Title>
<Typography.Title
level={4}
>{`B/R Hrs:${lbrHrsData?.jobs_by_pk.labhrs?.aggregate.sum.mod_lb_hrs}/${lbrHrsData?.jobs_by_pk.larhrs?.aggregate.sum.mod_lb_hrs}`}</Typography.Title>
</Space>
<LayoutFormRow grow>
<Form.Item
name="start"

View File

@@ -208,6 +208,7 @@ export const QUERY_LBR_HRS_BY_PK = gql`
query QUERY_LBR_HRS_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
id
ro_number
labhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -535,6 +536,7 @@ export const GET_JOB_BY_PK = gql`
unq_seq
line_ind
line_desc
line_ref
part_type
oem_partno
db_price

File diff suppressed because one or more lines are too long

View File

@@ -76,7 +76,7 @@ exports.default = function ({
{
local: false,
federal: true,
state: jobline.tax_part,
state: (jobline.db_ref === "900511" || jobline.db_ref === "900510") ? true: jobline.tax_part,
},
bodyshop.md_responsibility_centers.sales_tax_codes
);

View File

@@ -1,4 +1,5 @@
const urlBuilder = require("./qbo").urlBuilder;
const StandardizeName = require("./qbo").StandardizeName;
const path = require("path");
require("dotenv").config({
path: path.resolve(
@@ -114,7 +115,9 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
url: urlBuilder(
qbo_realmId,
"query",
`select * From vendor where DisplayName = '${bill.vendor.name}'`
`select * From vendor where DisplayName = '${StandardizeName(
bill.vendor.name
)}'`
),
method: "POST",
headers: {
@@ -152,7 +155,7 @@ async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
body: JSON.stringify(Vendor),
});
setNewRefreshToken(req.user.email, result);
return result && result.Vendor;
return result && result.json && result.json.Vendor;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error:
@@ -180,9 +183,9 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
DocNumber: bill.invoice_number,
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
Memo: `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 || ""} OWNER ${
bill.job.ownr_fn || ""
} ${bill.job.ownr_ln || ""} ${bill.job.ownr_co_nm || ""}`,
Line: bill.billlines.map((il) =>
generateBillLine(
il,
@@ -211,7 +214,7 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
body: JSON.stringify(billQbo),
});
setNewRefreshToken(req.user.email, result);
return result && result.Bill;
return result && result.json && result.json.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error:
@@ -245,6 +248,8 @@ const generateBillLine = (
costCenters
) => {
const account = costCenters.find((c) => c.name === billLine.cost_center);
console.log(account.accountname, accounts[account.accountname]);
return {
DetailType: "AccountBasedExpenseLineDetail",

View File

@@ -27,6 +27,7 @@ const {
} = require("../qbo/qbo-receivables");
const { urlBuilder } = require("./qbo");
const { DineroQbFormat } = require("../accounting-constants");
const { findTaxCode } = require("../qb-receivables-lines");
exports.default = async (req, res) => {
const oauthClient = new OAuthClient({
@@ -135,7 +136,17 @@ exports.default = async (req, res) => {
);
}
await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier);
if (payment.amount > 0) {
await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier);
} else {
await InsertCreditMemo(
oauthClient,
qbo_realmId,
req,
payment,
jobTier
);
}
ret.push({ paymentid: payment.id, success: true });
} catch (error) {
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
@@ -173,7 +184,8 @@ async function InsertPayment(
oauthClient,
qbo_realmId,
req,
payment.job.ro_number
payment.job.ro_number,
false
);
if (invoices && invoices.length !== 1) {
@@ -235,7 +247,13 @@ async function InsertPayment(
throw error;
}
}
async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) {
async function QueryMetaData(
oauthClient,
qbo_realmId,
req,
ro_number,
isCreditMemo
) {
const invoice = await oauthClient.makeApiCall({
url: urlBuilder(
qbo_realmId,
@@ -288,8 +306,50 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) {
// classes.json.QueryResponse.Class.forEach((t) => {
// accountMapping[t.Name] = t.Id;
// });
let ret = {};
if (isCreditMemo) {
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const items = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "query", `select * From Item`),
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
setNewRefreshToken(req.user.email, items);
const itemMapping = {};
items.json &&
items.json.QueryResponse &&
items.json.QueryResponse.Item &&
items.json.QueryResponse.Item.forEach((t) => {
itemMapping[t.Name] = t.Id;
});
const taxCodeMapping = {};
taxCodes.json &&
taxCodes.json.QueryResponse &&
taxCodes.json.QueryResponse.TaxCode &&
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
taxCodeMapping[t.Name] = t.Id;
});
ret = {
...ret,
items: itemMapping,
taxCodes: taxCodeMapping,
};
}
return {
...ret,
paymentMethods: paymentMethodMapping,
invoices:
invoice.json &&
@@ -297,3 +357,87 @@ async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) {
invoice.json.QueryResponse.Invoice,
};
}
async function InsertCreditMemo(
oauthClient,
qbo_realmId,
req,
payment,
parentRef
) {
const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData(
oauthClient,
qbo_realmId,
req,
payment.job.ro_number,
true
);
if (invoices && invoices.length !== 1) {
throw new Error(
`More than 1 invoice with DocNumber ${payment.ro_number} found.`
);
}
const paymentQbo = {
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
...(invoices && invoices[0]
? { InvoiceRef: { value: invoices[0].Id } }
: {}),
Line: [
{
DetailType: "SalesItemLineDetail",
Amount: Dinero({ amount: Math.round(payment.amount * -100) }).toFormat(
DineroQbFormat
),
SalesItemLineDetail: {
ItemRef: {
value:
items[
payment.job.bodyshop.md_responsibility_centers.refund
.accountitem
],
},
Qty: 1,
TaxCodeRef: {
value:
taxCodes[
findTaxCode(
{
local: false,
federal: false,
state: false,
},
payment.job.bodyshop.md_responsibility_centers.sales_tax_codes
)
],
},
},
},
],
};
logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
paymentQbo,
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(qbo_realmId, "creditmemo"),
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(paymentQbo),
});
setNewRefreshToken(req.user.email, result);
return result && result.Bill;
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
error: error && error.message,
method: "InsertCreditMemo",
});
throw error;
}
}

View File

@@ -1,4 +1,6 @@
const urlBuilder = require("./qbo").urlBuilder;
const StandardizeName = require("./qbo").StandardizeName;
const path = require("path");
require("dotenv").config({
path: path.resolve(
@@ -166,7 +168,9 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
url: urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${job.ins_co_nm}'`
`select * From Customer where DisplayName = '${StandardizeName(
job.ins_co_nm
)}'`
),
method: "POST",
headers: {
@@ -228,7 +232,9 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job) {
url: urlBuilder(
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${ownerName}'`
`select * From Customer where DisplayName = '${StandardizeName(
ownerName
)}'`
),
method: "POST",
headers: {
@@ -458,7 +464,7 @@ async function InsertInvoice(
body: JSON.stringify(invoiceObj),
});
setNewRefreshToken(req.user.email, result);
return result && result.Invoice;
return result && result.json && result.json.Invoice;
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,

View File

@@ -14,7 +14,12 @@ function urlBuilder(realmId, object, query = null) {
}`;
}
function StandardizeName(str) {
return str.replace(new RegExp(/'/g), "\\'");
}
exports.urlBuilder = urlBuilder;
exports.StandardizeName = StandardizeName;
exports.callback = require("./qbo-callback").default;
exports.authorize = require("./qbo-authorize").default;
exports.refresh = require("./qbo-callback").refresh;

View File

@@ -152,7 +152,7 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
QBXML: {
QBXMLMsgsRq: {
"@onError": "continueOnError",
CreditMemoAddRq: {
wMemoAddRq: {
CreditMemoAdd: {
CustomerRef: {
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3

View File

@@ -15,7 +15,7 @@ const CalcualteAllocations = require("./cdk-calculate-allocations").default;
const moment = require("moment");
const replaceSpecialRegex = `[^a-zA-Z0-9 .,\n #]+/g`;
const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g;
exports.default = async function (socket, { txEnvelope, jobid }) {
socket.logEvents = [];
@@ -584,15 +584,21 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
addressLine:
socket.JobData.ownr_addr1 &&
socket.JobData.ownr_addr1.replace(replaceSpecialRegex, ""),
city: socket.JobData.ownr_city&& socket.JobData.ownr_city.replace(replaceSpecialRegex, ""),
country: null,
city:
socket.JobData.ownr_city &&
socket.JobData.ownr_city.replace(replaceSpecialRegex, ""),
country:
socket.JobData.ownr_ctry &&
socket.JobData.ownr_ctry.replace(replaceSpecialRegex, ""),
postalCode:
socket.JobData.ownr_zip &&
socket.JobData.ownr_zip //TODO Need to remove for US Based customers.
.toUpperCase()
.replace(/\W/g, "")
.replace(/(...)/, "$1 "),
stateOrProvince: socket.JobData.ownr_st&&socket.JobData.ownr_st.replace(replaceSpecialRegex, ""),
stateOrProvince:
socket.JobData.ownr_st &&
socket.JobData.ownr_st.replace(replaceSpecialRegex, ""),
},
contactInfo: {
mainTelephoneNumber: {

View File

@@ -174,6 +174,7 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
ownr_ph2
ownr_zip
ownr_city
ownr_ctry
ownr_st
ownr_ea
ins_co_nm
@@ -797,6 +798,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
line_ind
line_desc
part_type
line_ref
oem_partno
db_price
act_price

View File

@@ -486,7 +486,9 @@ function CalculateTaxesTotals(job, otherTotals) {
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
.multiply(val.part_qty || 0)
.add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0
val.prt_dsmk_m &&
val.prt_dsmk_m !== 0 &&
DiscountNotAlreadyCounted(val, job.joblines)
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100),
@@ -566,3 +568,9 @@ function CalculateTaxesTotals(job, otherTotals) {
}
exports.default = Totals;
function DiscountNotAlreadyCounted(jobline, joblines) {
if (jobline.db_ref !== "900510") return true;
const ParentLine = joblines.find((j) => j.unq_seq === jobline.line_ref);
return ParentLine && !(ParentLine.prt_dsmk_m && ParentLine.prt_dsmk_m !== 0);
}