IO-2054 QBO Split AR
This commit is contained in:
@@ -24557,6 +24557,53 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>qb_multiple_payers</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>amount</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>name</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>
|
||||
<concept_node>
|
||||
<name>queued_for_parts</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -4535,6 +4535,24 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={<div>Multiple Payers Item</div>}>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenter_accountitem")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={[
|
||||
"md_responsibility_centers",
|
||||
"qb_multiple_payers",
|
||||
"accountitem",
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Typography.Title level={4}>
|
||||
{t("bodyshop.labels.responsibilitycenters.sales_tax_codes")}
|
||||
</Typography.Title>
|
||||
|
||||
@@ -1890,6 +1890,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
actual_in
|
||||
kmin
|
||||
kmout
|
||||
qb_multiple_payers
|
||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||
id
|
||||
removed
|
||||
|
||||
@@ -1,34 +1,38 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
notification,
|
||||
Popconfirm,
|
||||
Space,
|
||||
Alert,
|
||||
Button,
|
||||
Divider,
|
||||
PageHeader,
|
||||
InputNumber,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
notification,
|
||||
PageHeader,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
//import { useHistory } from "react-router-dom";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import moment from "moment";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
|
||||
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
import CurrencyInput from "../../components/form-items-formatted/currency-form-item.component";
|
||||
import JobsScoreboardAdd from "../../components/job-scoreboard-add-button/job-scoreboard-add-button.component";
|
||||
import JobsCloseAutoAllocate from "../../components/jobs-close-auto-allocate/jobs-close-auto-allocate.component";
|
||||
import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.component";
|
||||
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
|
||||
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
|
||||
import DateTimePicker from "../../components/form-date-time-picker/form-date-time-picker.component";
|
||||
import moment from "moment";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -42,6 +46,11 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
||||
// const history = useHistory();
|
||||
const [closeJob] = useMutation(UPDATE_JOB);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { Qb_Multi_Ar } = useTreatments(
|
||||
["Qb_Multi_Ar"],
|
||||
{},
|
||||
bodyshop && bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const handleFinish = async ({ removefromproduction, ...values }) => {
|
||||
setLoading(true);
|
||||
@@ -65,6 +74,9 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
||||
kmout: values.kmout,
|
||||
dms_allocation: values.dms_allocation,
|
||||
...(removefromproduction ? { inproduction: false } : {}),
|
||||
...(values.qb_multiple_payers
|
||||
? { qb_multiple_payers: values.qb_multiple_payers }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
|
||||
@@ -127,6 +139,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
||||
kmin: job.kmin,
|
||||
kmout: job.kmout,
|
||||
dms_allocation: job.dms_allocation,
|
||||
qb_multiple_payers: job.qb_multiple_payers,
|
||||
}}
|
||||
scrollToFirstError
|
||||
>
|
||||
@@ -312,6 +325,76 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
|
||||
</Form.Item>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
{Qb_Multi_Ar.treatment === "on" && (
|
||||
<>
|
||||
<Form.List name={["qb_multiple_payers"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Space>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.qb_multiple_payers.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
style={{ minWidth: "12rem" }}
|
||||
disabled={jobRO}
|
||||
>
|
||||
{bodyshop.md_ins_cos.map((s) => (
|
||||
<Select.Option key={s.name} value={s.name}>
|
||||
{s.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.qb_multiple_payers.amount")}
|
||||
key={`${index}amount`}
|
||||
name={[field.name, "amount"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
<DeleteFilled
|
||||
disabled={jobRO}
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
disabled={jobRO}
|
||||
onClick={() => {
|
||||
if (fields.length < 3) add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("jobs.actions.dms.addpayer")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</>
|
||||
)}
|
||||
<Divider />
|
||||
<JobsCloseLines job={job} />
|
||||
</Form>
|
||||
|
||||
@@ -1468,6 +1468,10 @@
|
||||
"production_vars": {
|
||||
"note": "Production Note"
|
||||
},
|
||||
"qb_multiple_payers": {
|
||||
"amount": "Amount",
|
||||
"name": "Name"
|
||||
},
|
||||
"queued_for_parts": "Queued for Parts",
|
||||
"rate_ats": "ATS Rate",
|
||||
"rate_la1": "LA1",
|
||||
|
||||
@@ -1468,6 +1468,10 @@
|
||||
"production_vars": {
|
||||
"note": ""
|
||||
},
|
||||
"qb_multiple_payers": {
|
||||
"amount": "",
|
||||
"name": ""
|
||||
},
|
||||
"queued_for_parts": "",
|
||||
"rate_ats": "",
|
||||
"rate_la1": "Tarifa LA1",
|
||||
|
||||
@@ -1468,6 +1468,10 @@
|
||||
"production_vars": {
|
||||
"note": ""
|
||||
},
|
||||
"qb_multiple_payers": {
|
||||
"amount": "",
|
||||
"name": ""
|
||||
},
|
||||
"queued_for_parts": "",
|
||||
"rate_ats": "",
|
||||
"rate_la1": "Taux LA1",
|
||||
|
||||
@@ -3139,6 +3139,7 @@
|
||||
- po_number
|
||||
- policy_no
|
||||
- production_vars
|
||||
- qb_multiple_payers
|
||||
- queued_for_parts
|
||||
- rate_ats
|
||||
- rate_la1
|
||||
@@ -3401,6 +3402,7 @@
|
||||
- po_number
|
||||
- policy_no
|
||||
- production_vars
|
||||
- qb_multiple_payers
|
||||
- queued_for_parts
|
||||
- rate_ats
|
||||
- rate_la1
|
||||
@@ -3673,6 +3675,7 @@
|
||||
- po_number
|
||||
- policy_no
|
||||
- production_vars
|
||||
- qb_multiple_payers
|
||||
- queued_for_parts
|
||||
- rate_ats
|
||||
- rate_la1
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "qb_multiple_payers" jsonb
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "qb_multiple_payers" jsonb
|
||||
null;
|
||||
43065
logs/oAuthClient-log.log
43065
logs/oAuthClient-log.log
File diff suppressed because one or more lines are too long
@@ -648,6 +648,56 @@ exports.default = function ({
|
||||
});
|
||||
}
|
||||
|
||||
//Check if there are multiple payers. If there are, add a deduction line and make sure we create new invoices.
|
||||
|
||||
if (
|
||||
jobs_by_pk.qb_multiple_payers &&
|
||||
jobs_by_pk.qb_multiple_payers.length > 0
|
||||
) {
|
||||
jobs_by_pk.qb_multiple_payers.forEach((payer) => {
|
||||
if (qbo) {
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero({ amount: (payer.amount || 0) * 100 * -1 }).toFormat(
|
||||
DineroQbFormat
|
||||
),
|
||||
SalesItemLineDetail: {
|
||||
...(jobs_by_pk.class
|
||||
? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||
: {}),
|
||||
ItemRef: {
|
||||
value:
|
||||
items[responsibilityCenters.qb_multiple_payers?.accountitem],
|
||||
},
|
||||
Qty: 1,
|
||||
TaxCodeRef: {
|
||||
value:
|
||||
taxCodes[
|
||||
findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: false,
|
||||
state: false,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
)
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
InvoiceLineAdd.push({
|
||||
ItemRef: {
|
||||
FullName: responsibilityCenters.qb_multiple_payers?.accountitem,
|
||||
},
|
||||
Desc: `${payer.name} Liability`,
|
||||
Amount: Dinero({ amount: (payer.amount || 0) * 100 * -1 }).toFormat(
|
||||
DineroQbFormat
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return InvoiceLineAdd;
|
||||
};
|
||||
|
||||
@@ -667,3 +717,65 @@ const findTaxCode = ({ local, state, federal }, taxcode) => {
|
||||
}
|
||||
};
|
||||
exports.findTaxCode = findTaxCode;
|
||||
|
||||
exports.createMultiQbPayerLines = function ({
|
||||
bodyshop,
|
||||
jobs_by_pk,
|
||||
qbo = false,
|
||||
items,
|
||||
taxCodes,
|
||||
classes,
|
||||
payer,
|
||||
}) {
|
||||
const InvoiceLineAdd = [];
|
||||
const responsibilityCenters = bodyshop.md_responsibility_centers;
|
||||
|
||||
const invoiceLineHash = {}; //The hash of cost and profit centers based on the center name.
|
||||
|
||||
if (qbo) {
|
||||
//Going to always assume that we need to apply GST and PST for labor.
|
||||
const taxAccountCode = findTaxCode(
|
||||
{
|
||||
local: false,
|
||||
federal: false,
|
||||
state: false,
|
||||
},
|
||||
bodyshop.md_responsibility_centers.sales_tax_codes
|
||||
);
|
||||
const QboTaxId = taxCodes[taxAccountCode];
|
||||
InvoiceLineAdd.push({
|
||||
DetailType: "SalesItemLineDetail",
|
||||
Amount: Dinero({
|
||||
amount: Math.round((payer.amount || 0) * 100),
|
||||
}).toFormat(DineroQbFormat),
|
||||
SalesItemLineDetail: {
|
||||
...(jobs_by_pk.class
|
||||
? { ClassRef: { value: classes[jobs_by_pk.class] } }
|
||||
: {}),
|
||||
ItemRef: {
|
||||
value: items[responsibilityCenters.qb_multiple_payers?.accountitem],
|
||||
},
|
||||
TaxCodeRef: {
|
||||
value: QboTaxId,
|
||||
},
|
||||
Qty: 1,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
InvoiceLineAdd.push({
|
||||
ItemRef: {
|
||||
FullName: responsibilityCenters.qb_multiple_payers?.accountitem,
|
||||
},
|
||||
Desc: `${payer.name} Liability`,
|
||||
Quantity: 1,
|
||||
Amount: Dinero({
|
||||
amount: Math.round((payer.amount || 0) * 100),
|
||||
}).toFormat(DineroQbFormat),
|
||||
SalesTaxCodeRef: {
|
||||
FullName: "E",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return InvoiceLineAdd;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ const moment = require("moment-timezone");
|
||||
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
|
||||
const { createMultiQbPayerLines } = require("../qb-receivables-lines");
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
const oauthClient = new OAuthClient({
|
||||
@@ -115,7 +116,13 @@ exports.default = async (req, res) => {
|
||||
}
|
||||
|
||||
//Query for the Job or Create it.
|
||||
jobTier = await QueryJob(oauthClient, qbo_realmId, req, job);
|
||||
jobTier = await QueryJob(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
isThreeTier ? ownerCustomerTier : null // ownerCustomerTier || insCoCustomerTier
|
||||
);
|
||||
|
||||
// Need to validate that the job tier is associated to the right individual?
|
||||
|
||||
@@ -140,6 +147,65 @@ exports.default = async (req, res) => {
|
||||
jobTier
|
||||
);
|
||||
|
||||
if (job.qb_multiple_payers && job.qb_multiple_payers.length > 0) {
|
||||
for (const [index, payer] of job.qb_multiple_payers.entries()) {
|
||||
//do the thing.
|
||||
|
||||
//Create the source level.
|
||||
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
||||
|
||||
//Insert the insurance company tier.
|
||||
//Query for top level customer, the insurance company name.
|
||||
insCoCustomerTier = await QueryInsuranceCo(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
{ ...job, ins_co_nm: payer.name }
|
||||
);
|
||||
if (!insCoCustomerTier) {
|
||||
//Creating the Insurance Customer.
|
||||
insCoCustomerTier = await InsertInsuranceCo(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
{ ...job, ins_co_nm: payer.name },
|
||||
bodyshop
|
||||
);
|
||||
}
|
||||
//Query for the Job or Create it.
|
||||
jobTier = await QueryJob(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
insCoCustomerTier
|
||||
);
|
||||
// Need to validate that the job tier is associated to the right individual?
|
||||
if (!jobTier) {
|
||||
jobTier = await InsertJob(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
insCoCustomerTier
|
||||
);
|
||||
}
|
||||
|
||||
//Create the RO level
|
||||
|
||||
await InsertInvoiceMultiPayerInvoice(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
bodyshop,
|
||||
jobTier,
|
||||
payer,
|
||||
`-${index + 1}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// //No error. Mark the job exported & insert export log.
|
||||
if (elgen) {
|
||||
const result = await client
|
||||
@@ -212,7 +278,7 @@ async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
|
||||
"query",
|
||||
`select * From Customer where DisplayName = '${StandardizeName(
|
||||
job.ins_co_nm.trim()
|
||||
)}'`
|
||||
)}' and Active = true`
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -284,7 +350,7 @@ async function QueryOwner(oauthClient, qbo_realmId, req, job) {
|
||||
"query",
|
||||
`select * From Customer where DisplayName = '${StandardizeName(
|
||||
ownerName
|
||||
)}'`
|
||||
)}' and Active = true`
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -348,12 +414,12 @@ async function InsertOwner(
|
||||
}
|
||||
}
|
||||
exports.InsertOwner = InsertOwner;
|
||||
async function QueryJob(oauthClient, qbo_realmId, req, job) {
|
||||
async function QueryJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Customer where DisplayName = '${job.ro_number}'`
|
||||
`select * From Customer where DisplayName = '${job.ro_number}' and Active = true`
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
@@ -365,9 +431,14 @@ async function QueryJob(oauthClient, qbo_realmId, req, job) {
|
||||
result.json &&
|
||||
result.json.QueryResponse &&
|
||||
result.json.QueryResponse.Customer &&
|
||||
result.json.QueryResponse.Customer[0]
|
||||
(parentTierRef
|
||||
? result.json.QueryResponse.Customer.find(
|
||||
(x) => x.ParentRef.value === parentTierRef.Id
|
||||
)
|
||||
: result.json.QueryResponse.Customer[0])
|
||||
);
|
||||
}
|
||||
|
||||
exports.QueryJob = QueryJob;
|
||||
async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
||||
const Customer = {
|
||||
@@ -602,3 +673,137 @@ async function InsertInvoice(
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function InsertInvoiceMultiPayerInvoice(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
bodyshop,
|
||||
parentTierRef,
|
||||
payer,
|
||||
suffix
|
||||
) {
|
||||
const { items, taxCodes, classes } = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req
|
||||
);
|
||||
const InvoiceLineAdd = createMultiQbPayerLines({
|
||||
bodyshop,
|
||||
jobs_by_pk: job,
|
||||
qbo: true,
|
||||
items,
|
||||
taxCodes,
|
||||
classes,
|
||||
payer,
|
||||
suffix,
|
||||
});
|
||||
|
||||
const invoiceObj = {
|
||||
Line: InvoiceLineAdd,
|
||||
TxnDate: moment(job.date_invoiced)
|
||||
.tz(bodyshop.timezone)
|
||||
.format("YYYY-MM-DD"),
|
||||
DocNumber: job.ro_number + suffix,
|
||||
...(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}` : ``
|
||||
} Vehicle:${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
|
||||
job.v_model_desc || ""
|
||||
} ${job.v_vin || ""} ${job.plate_no || ""} `.trim(),
|
||||
},
|
||||
CustomerRef: {
|
||||
value: parentTierRef.Id,
|
||||
},
|
||||
...(bodyshop.accountingconfig.qbo_departmentid &&
|
||||
bodyshop.accountingconfig.qbo_departmentid.trim() !== "" && {
|
||||
DepartmentRef: { value: bodyshop.accountingconfig.qbo_departmentid },
|
||||
}),
|
||||
CustomField: [
|
||||
...(bodyshop.accountingconfig.ReceivableCustomField1
|
||||
? [
|
||||
{
|
||||
DefinitionId: "1",
|
||||
StringValue:
|
||||
job[bodyshop.accountingconfig.ReceivableCustomField1],
|
||||
Type: "StringType",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(bodyshop.accountingconfig.ReceivableCustomField2
|
||||
? [
|
||||
{
|
||||
DefinitionId: "2",
|
||||
StringValue:
|
||||
job[bodyshop.accountingconfig.ReceivableCustomField2],
|
||||
Type: "StringType",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(bodyshop.accountingconfig.ReceivableCustomField3
|
||||
? [
|
||||
{
|
||||
DefinitionId: "3",
|
||||
StringValue:
|
||||
job[bodyshop.accountingconfig.ReceivableCustomField3],
|
||||
Type: "StringType",
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
...(bodyshop.accountingconfig &&
|
||||
bodyshop.accountingconfig.qbo &&
|
||||
bodyshop.accountingconfig.qbo_usa &&
|
||||
bodyshop.region_config.includes("CA_") && {
|
||||
TxnTaxDetail: {
|
||||
TxnTaxCodeRef: {
|
||||
value:
|
||||
taxCodes[
|
||||
bodyshop.md_responsibility_centers.taxes.state.accountitem
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
...(bodyshop.accountingconfig.printlater
|
||||
? { PrintStatus: "NeedToPrint" }
|
||||
: {}),
|
||||
...(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, {
|
||||
invoiceObj,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(qbo_realmId, "invoice"),
|
||||
method: "POST",
|
||||
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(invoiceObj),
|
||||
});
|
||||
setNewRefreshToken(req.user.email, result);
|
||||
return result && result.json && result.json.Invoice;
|
||||
} catch (error) {
|
||||
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
||||
error,
|
||||
method: "InsertOwner",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,6 +191,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
storage_payable
|
||||
adjustment_bottom_line
|
||||
state_tax_rate
|
||||
qb_multiple_payers
|
||||
owner {
|
||||
accountingid
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user