IO-256 QBO SS Realm ID

This commit is contained in:
Patrick Fic
2021-10-20 14:33:31 -07:00
parent f3c44f8dd1
commit 14af45baf0
17 changed files with 4270 additions and 131 deletions

View File

@@ -210,7 +210,7 @@ export function ScheduleEventComponent({
job: event.job,
previousEvent: event.id,
color: event.color,
alt_transport: event.alt_transport,
alt_transport: event.job && event.job.alt_transport,
note: event.note,
},
});

View File

@@ -182,16 +182,7 @@ export function JobsCloseExportButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={
disabled ||
(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
!cookies.qbo_realmId)
}
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -175,16 +175,7 @@ export function JobsExportAllButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={
disabled ||
(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
!cookies.qbo_realmId)
}
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -174,16 +174,7 @@ export function PayableExportAll({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={
disabled ||
(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
!cookies.qbo_realmId)
}
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -176,16 +176,7 @@ export function PayableExportButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={
disabled ||
(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
!cookies.qbo_realmId)
}
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -177,16 +177,7 @@ export function PaymentExportButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={
disabled ||
(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
!cookies.qbo_realmId)
}
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -158,16 +158,7 @@ export function PaymentsExportAllButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={
disabled ||
(bodyshop.accountingconfig &&
bodyshop.accountingconfig.qbo &&
!cookies.qbo_realmId)
}
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -24,20 +24,20 @@ export default function QboAuthorizeComponent() {
const hasBeenCalledBack = code && realmId && state;
if (hasBeenCalledBack) {
setCookie("qbo_code", code, { path: "/" });
setCookie("qbo_state", state, { path: "/" });
// setCookie("qbo_code", code, { path: "/" });
// setCookie("qbo_state", state, { path: "/" });
let expires = new Date();
expires.setTime(expires.getTime() + 8726400 * 1000);
// let expires = new Date();
// expires.setTime(expires.getTime() + 8726400 * 1000);
setCookie("qbo_realmId", realmId, {
path: "/",
expires,
// setCookie("qbo_realmId", realmId, {
// path: "/",
// expires,
...(process.env.NODE_ENV !== "development"
? { domain: `.${window.location.host}` }
: {}),
});
// ...(process.env.NODE_ENV !== "development"
// ? { domain: `.${window.location.host}` }
// : {}),
// });
history.push({ pathname: `/manage/accounting/receivables` });
}
@@ -52,9 +52,7 @@ export default function QboAuthorizeComponent() {
src={QboSignIn}
style={{ cursor: "pointer" }}
/>
{!cookies.qbo_realmId && (
<Tag color="red">No QuickBooks company has been connected.</Tag>
)}
{error && JSON.parse(decodeURIComponent(error)).error_description}
</Space>
);

View File

@@ -203,6 +203,7 @@
- authlevel
- default_prod_list_view
- id
- qbo_realmId
- shopid
- useremail
filter:
@@ -216,6 +217,7 @@
- active
- authlevel
- default_prod_list_view
- qbo_realmId
filter:
bodyshop:
associations:

View File

@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."associations" add column "qbo_realmId" text
-- null;

View File

@@ -0,0 +1,2 @@
alter table "public"."associations" add column "qbo_realmId" text
null;

File diff suppressed because one or more lines are too long

View File

@@ -44,9 +44,10 @@ exports.default = async (req, res) => {
)}`
);
} else {
await client.request(queries.SET_QBO_AUTH, {
await client.request(queries.SET_QBO_AUTH_WITH_REALM, {
email: params.state,
qbo_auth: { ...authResponse.json, createdAt: Date.now() },
qbo_realmId: params.realmId,
});
logger.log(
"qbo-callback-create-token-success",

View File

@@ -34,10 +34,14 @@ exports.default = async (req, res) => {
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
email: req.user.email,
});
const { qbo_realmId } = response.associations[0];
oauthClient.setToken(response.associations[0].qbo_auth);
await refreshOauthToken(oauthClient, req);
if (!qbo_realmId) {
res.status(401).json({ error: "No company associated." });
return;
}
await refreshOauthToken(oauthClient, qbo_realmId, req);
const BearerToken = req.headers.authorization;
const { bills: billsToQuery } = req.body;
@@ -60,14 +64,26 @@ exports.default = async (req, res) => {
for (const bill of bills) {
try {
let vendorRecord;
vendorRecord = await QueryVendorRecord(oauthClient, req, bill);
vendorRecord = await QueryVendorRecord(
oauthClient,
qbo_realmId,
qbo_realmId,
req,
bill
);
if (!vendorRecord) {
vendorRecord = await InsertVendorRecord(oauthClient, req, bill);
vendorRecord = await InsertVendorRecord(
oauthClient,
qbo_realmId,
req,
bill
);
}
const insertResults = await InsertBill(
oauthClient,
qbo_realmId,
req,
bill,
vendorRecord
@@ -93,11 +109,11 @@ exports.default = async (req, res) => {
}
};
async function QueryVendorRecord(oauthClient, req, bill) {
async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
"query",
`select * From vendor where DisplayName = '${bill.vendor.name}'`
),
@@ -123,13 +139,13 @@ async function QueryVendorRecord(oauthClient, req, bill) {
throw error;
}
}
async function InsertVendorRecord(oauthClient, req, bill) {
async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
const Vendor = {
DisplayName: bill.vendor.name,
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "vendor"),
url: urlBuilder(qbo_realmId, "vendor"),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -149,8 +165,12 @@ async function InsertVendorRecord(oauthClient, req, bill) {
}
}
async function InsertBill(oauthClient, req, bill, vendor) {
const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, req);
async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
const { accounts, taxCodes, classes } = await QueryMetaData(
oauthClient,
qbo_realmId,
req
);
const billQbo = {
VendorRef: {
@@ -182,7 +202,7 @@ async function InsertBill(oauthClient, req, bill, vendor) {
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
bill.is_credit_memo ? "vendorcredit" : "bill"
),
method: "POST",
@@ -248,10 +268,10 @@ const generateBillLine = (
};
};
async function QueryMetaData(oauthClient, req) {
async function QueryMetaData(oauthClient, qbo_realmId, req) {
const accounts = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
"query",
`select * From Account where AccountType = 'Cost of Goods Sold'`
),
@@ -262,7 +282,7 @@ async function QueryMetaData(oauthClient, req) {
});
setNewRefreshToken(req.user.email, accounts);
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From TaxCode`),
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -270,7 +290,7 @@ async function QueryMetaData(oauthClient, req) {
});
const classes = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
method: "POST",
headers: {
"Content-Type": "application/json",

View File

@@ -42,10 +42,13 @@ exports.default = async (req, res) => {
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
email: req.user.email,
});
const { qbo_realmId } = response.associations[0];
oauthClient.setToken(response.associations[0].qbo_auth);
await refreshOauthToken(oauthClient, req);
if (!qbo_realmId) {
res.status(401).json({ error: "No company associated." });
return;
}
await refreshOauthToken(oauthClient, qbo_realmId, req);
const BearerToken = req.headers.authorization;
const { payments: paymentsToQuery } = req.body;
@@ -80,6 +83,7 @@ exports.default = async (req, res) => {
//Query for top level customer, the insurance company name.
insCoCustomerTier = await QueryInsuranceCo(
oauthClient,
qbo_realmId,
req,
payment.job
);
@@ -87,6 +91,7 @@ exports.default = async (req, res) => {
//Creating the Insurance Customer.
insCoCustomerTier = await InsertInsuranceCo(
oauthClient,
qbo_realmId,
req,
payment.job,
bodyshop
@@ -96,11 +101,17 @@ exports.default = async (req, res) => {
if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
//Insert the name/owner and account for whether the source should be the ins co in 3 tier..
ownerCustomerTier = await QueryOwner(oauthClient, req, payment.job);
ownerCustomerTier = await QueryOwner(
oauthClient,
qbo_realmId,
req,
payment.job
);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(
oauthClient,
qbo_realmId,
req,
payment.job,
isThreeTier,
@@ -110,20 +121,21 @@ exports.default = async (req, res) => {
}
//Query for the Job or Create it.
jobTier = await QueryJob(oauthClient, req, payment.job);
jobTier = await QueryJob(oauthClient, qbo_realmId, req, payment.job);
// Need to validate that the job tier is associated to the right individual?
if (!jobTier) {
jobTier = await InsertJob(
oauthClient,
qbo_realmId,
req,
payment.job,
ownerCustomerTier || insCoCustomerTier
);
}
await InsertPayment(oauthClient, req, payment, jobTier);
await InsertPayment(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, {
@@ -150,9 +162,16 @@ exports.default = async (req, res) => {
}
};
async function InsertPayment(oauthClient, req, payment, parentRef) {
async function InsertPayment(
oauthClient,
qbo_realmId,
req,
payment,
parentRef
) {
const { paymentMethods, invoices } = await QueryMetaData(
oauthClient,
qbo_realmId,
req,
payment.job.ro_number
);
@@ -199,7 +218,7 @@ async function InsertPayment(oauthClient, req, payment, parentRef) {
});
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "payment"),
url: urlBuilder(qbo_realmId, "payment"),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -216,10 +235,10 @@ async function InsertPayment(oauthClient, req, payment, parentRef) {
throw error;
}
}
async function QueryMetaData(oauthClient, req, ro_number) {
async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) {
const invoice = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
"query",
`select * From Invoice where DocNumber = '${ro_number}'`
),
@@ -230,11 +249,7 @@ async function QueryMetaData(oauthClient, req, ro_number) {
});
const paymentMethods = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
"query",
`select * From PaymentMethod`
),
url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -243,7 +258,7 @@ async function QueryMetaData(oauthClient, req, ro_number) {
setNewRefreshToken(req.user.email, paymentMethods);
// const classes = await oauthClient.makeApiCall({
// url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
// url: urlBuilder(qbo_realmId, "query", `select * From Class`),
// method: "POST",
// headers: {
// "Content-Type": "application/json",

View File

@@ -34,7 +34,11 @@ exports.default = async (req, res) => {
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
email: req.user.email,
});
const { qbo_realmId } = response.associations[0];
if (!qbo_realmId) {
res.status(401).json({ error: "No company associated." });
return;
}
oauthClient.setToken(response.associations[0].qbo_auth);
await refreshOauthToken(oauthClient, req);
@@ -69,11 +73,17 @@ exports.default = async (req, res) => {
if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
//Insert the insurance company tier.
//Query for top level customer, the insurance company name.
insCoCustomerTier = await QueryInsuranceCo(oauthClient, req, job);
insCoCustomerTier = await QueryInsuranceCo(
oauthClient,
qbo_realmId,
req,
job
);
if (!insCoCustomerTier) {
//Creating the Insurance Customer.
insCoCustomerTier = await InsertInsuranceCo(
oauthClient,
qbo_realmId,
req,
job,
bodyshop
@@ -83,11 +93,17 @@ exports.default = async (req, res) => {
if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
//Insert the name/owner and account for whether the source should be the ins co in 3 tier..
ownerCustomerTier = await QueryOwner(oauthClient, req, job);
ownerCustomerTier = await QueryOwner(
oauthClient,
qbo_realmId,
req,
job
);
//Query for the owner itself.
if (!ownerCustomerTier) {
ownerCustomerTier = await InsertOwner(
oauthClient,
qbo_realmId,
req,
job,
isThreeTier,
@@ -97,13 +113,14 @@ exports.default = async (req, res) => {
}
//Query for the Job or Create it.
jobTier = await QueryJob(oauthClient, req, job);
jobTier = await QueryJob(oauthClient, qbo_realmId, req, job);
// Need to validate that the job tier is associated to the right individual?
if (!jobTier) {
jobTier = await InsertJob(
oauthClient,
qbo_realmId,
req,
job,
@@ -112,7 +129,14 @@ exports.default = async (req, res) => {
}
if (!req.body.custDataOnly) {
await InsertInvoice(oauthClient, req, job, bodyshop, jobTier);
await InsertInvoice(
oauthClient,
qbo_realmId,
req,
job,
bodyshop,
jobTier
);
}
ret.push({ jobid: job.id, success: true });
} catch (error) {
@@ -136,11 +160,11 @@ exports.default = async (req, res) => {
}
};
async function QueryInsuranceCo(oauthClient, req, job) {
async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${job.ins_co_nm}'`
),
@@ -165,7 +189,7 @@ async function QueryInsuranceCo(oauthClient, req, job) {
}
}
exports.QueryInsuranceCo = QueryInsuranceCo;
async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
const Customer = {
@@ -180,7 +204,7 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "customer"),
url: urlBuilder(qbo_realmId, "customer"),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -198,11 +222,11 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
}
}
exports.InsertInsuranceCo = InsertInsuranceCo;
async function QueryOwner(oauthClient, req, job) {
async function QueryOwner(oauthClient, qbo_realmId, req, job) {
const ownerName = generateOwnerTier(job, true, null);
const result = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${ownerName}'`
),
@@ -220,7 +244,14 @@ async function QueryOwner(oauthClient, req, job) {
);
}
exports.QueryOwner = QueryOwner;
async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
async function InsertOwner(
oauthClient,
qbo_realmId,
req,
job,
isThreeTier,
parentTierRef
) {
const ownerName = generateOwnerTier(job, true, null);
const Customer = {
DisplayName: ownerName,
@@ -242,7 +273,7 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "customer"),
url: urlBuilder(qbo_realmId, "customer"),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -260,10 +291,10 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
}
}
exports.InsertOwner = InsertOwner;
async function QueryJob(oauthClient, req, job) {
async function QueryJob(oauthClient, qbo_realmId, req, job) {
const result = await oauthClient.makeApiCall({
url: urlBuilder(
req.cookies.qbo_realmId,
qbo_realmId,
"query",
`select * From Customer where DisplayName = '${job.ro_number}'`
),
@@ -281,7 +312,7 @@ async function QueryJob(oauthClient, req, job) {
);
}
exports.QueryJob = QueryJob;
async function InsertJob(oauthClient, req, job, parentTierRef) {
async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
const Customer = {
DisplayName: job.ro_number,
BillAddr: {
@@ -299,7 +330,7 @@ async function InsertJob(oauthClient, req, job, parentTierRef) {
};
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "customer"),
url: urlBuilder(qbo_realmId, "customer"),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -317,9 +348,9 @@ async function InsertJob(oauthClient, req, job, parentTierRef) {
}
}
exports.InsertJob = InsertJob;
async function QueryMetaData(oauthClient, req) {
async function QueryMetaData(oauthClient, qbo_realmId, req) {
const items = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Item`),
url: urlBuilder(qbo_realmId, "query", `select * From Item`),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -327,7 +358,7 @@ async function QueryMetaData(oauthClient, req) {
});
setNewRefreshToken(req.user.email, items);
const taxCodes = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From TaxCode`),
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -335,7 +366,7 @@ async function QueryMetaData(oauthClient, req) {
});
const classes = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
method: "POST",
headers: {
"Content-Type": "application/json",
@@ -375,8 +406,19 @@ async function QueryMetaData(oauthClient, req) {
};
}
async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) {
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, req);
async function InsertInvoice(
oauthClient,
qbo_realmId,
req,
job,
bodyshop,
parentTierRef
) {
const { items, taxCodes, classes } = await QueryMetaData(
oauthClient,
qbo_realmId,
req
);
const InvoiceLineAdd = CreateInvoiceLines({
bodyshop,
jobs_by_pk: job,
@@ -407,7 +449,7 @@ async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) {
try {
const result = await oauthClient.makeApiCall({
url: urlBuilder(req.cookies.qbo_realmId, "invoice"),
url: urlBuilder(qbo_realmId, "invoice"),
method: "POST",
headers: {

View File

@@ -1194,9 +1194,17 @@ exports.GET_QBO_AUTH = `query GET_QBO_AUTH($email: String!) {
associations(where: {_and: {active: {_eq: true}, useremail: {_eq: $email}}}){
id
qbo_auth
qbo_realmId
}
}`;
exports.SET_QBO_AUTH_WITH_REALM = `mutation SET_QBO_AUTH($email: String!, $qbo_auth: jsonb!, $qbo_realmId: String) {
update_associations(_set: {qbo_auth: $qbo_auth, qbo_realmId: $qbo_realmId}, where: {_and: {active: {_eq: true}, useremail: {_eq: $email}}}){
affected_rows
}
}
`;
exports.SET_QBO_AUTH = `mutation SET_QBO_AUTH($email: String!, $qbo_auth: jsonb!) {
update_associations(_set: {qbo_auth: $qbo_auth}, where: {_and: {active: {_eq: true}, useremail: {_eq: $email}}}){
affected_rows