Merge branch 'test' into feature/pbs
This commit is contained in:
@@ -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
|
BabelEdit project file
|
||||||
@@ -16878,6 +16878,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>prt_dsmk_m</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>
|
<concept_node>
|
||||||
<name>prt_dsmk_p</name>
|
<name>prt_dsmk_p</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -8,8 +8,26 @@ import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
|||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||||
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
export default function AccountingPayablesTableComponent({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(AccountingPayablesTableComponent);
|
||||||
|
|
||||||
|
export function AccountingPayablesTableComponent({
|
||||||
|
bodyshop,
|
||||||
loading,
|
loading,
|
||||||
payments,
|
payments,
|
||||||
}) {
|
}) {
|
||||||
@@ -163,6 +181,9 @@ export default function AccountingPayablesTableComponent({
|
|||||||
loadingCallback={setTransInProgress}
|
loadingCallback={setTransInProgress}
|
||||||
completedCallback={setSelectedPayments}
|
completedCallback={setSelectedPayments}
|
||||||
/>
|
/>
|
||||||
|
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||||
|
<QboAuthorizeComponent />
|
||||||
|
)}
|
||||||
<Input
|
<Input
|
||||||
value={state.search}
|
value={state.search}
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
|
|||||||
@@ -259,7 +259,6 @@ export function DmsPostForm({ bodyshop, socket, job }) {
|
|||||||
))}
|
))}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
type="dashed"
|
|
||||||
disabled={!(fields.length < 3)}
|
disabled={!(fields.length < 3)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (fields.length < 3) add();
|
if (fields.length < 3) add();
|
||||||
|
|||||||
@@ -162,7 +162,11 @@ export function JobLinesComponent({
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<>
|
<>
|
||||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
<CurrencyFormatter>
|
||||||
|
{record.db_ref === "900510" || record.db_ref === "900511"
|
||||||
|
? record.prt_dsmk_m
|
||||||
|
: record.act_price}
|
||||||
|
</CurrencyFormatter>
|
||||||
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
||||||
<span
|
<span
|
||||||
style={{ marginLeft: ".2rem" }}
|
style={{ marginLeft: ".2rem" }}
|
||||||
|
|||||||
@@ -498,6 +498,12 @@ async function CheckTaxRates(estData, bodyshop) {
|
|||||||
) {
|
) {
|
||||||
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Set markup lines and tax lines as taxable.
|
||||||
|
//900510 is a mark up. 900510 is a discount.
|
||||||
|
if (jl.db_ref === "900510") {
|
||||||
|
estData.joblines.data[index].tax_part = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -32,6 +33,7 @@ export function JobsCloseExportButton({
|
|||||||
const [updateJob] = useMutation(UPDATE_JOB);
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [cookies] = useCookies();
|
||||||
|
|
||||||
const handleQbxml = async () => {
|
const handleQbxml = async () => {
|
||||||
//Check if it's a CDK setup.
|
//Check if it's a CDK setup.
|
||||||
@@ -45,10 +47,13 @@ export function JobsCloseExportButton({
|
|||||||
//Check if it's a QBO Setup.
|
//Check if it's a QBO Setup.
|
||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
PartnerResponse = await axios.post(
|
||||||
withCredentials: true,
|
`/qbo/receivables`,
|
||||||
jobIds: [jobId],
|
{
|
||||||
});
|
jobIds: [jobId],
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
//Default is QBD
|
//Default is QBD
|
||||||
|
|
||||||
@@ -177,7 +182,16 @@ export function JobsCloseExportButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button
|
||||||
|
onClick={handleQbxml}
|
||||||
|
loading={loading}
|
||||||
|
disabled={
|
||||||
|
disabled ||
|
||||||
|
(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
!cookies.qbo_realmId)
|
||||||
|
}
|
||||||
|
>
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
<th>{t("joblines.fields.line_desc")}</th>
|
<th>{t("joblines.fields.line_desc")}</th>
|
||||||
<th>{t("joblines.fields.part_type")}</th>
|
<th>{t("joblines.fields.part_type")}</th>
|
||||||
<th>{t("joblines.fields.act_price")}</th>
|
<th>{t("joblines.fields.act_price")}</th>
|
||||||
|
<th>{t("joblines.fields.prt_dsmk_m")}</th>
|
||||||
<th>{t("joblines.fields.op_code_desc")}</th>
|
<th>{t("joblines.fields.op_code_desc")}</th>
|
||||||
<th>{t("joblines.fields.mod_lbr_ty")}</th>
|
<th>{t("joblines.fields.mod_lbr_ty")}</th>
|
||||||
<th>{t("joblines.fields.mod_lb_hrs")}</th>
|
<th>{t("joblines.fields.mod_lb_hrs")}</th>
|
||||||
@@ -70,6 +71,16 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
<ReadOnlyFormItem type="currency" />
|
<ReadOnlyFormItem type="currency" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<Form.Item
|
||||||
|
span={2}
|
||||||
|
// label={t("joblines.fields.prt_dsmk_m")}
|
||||||
|
key={`${index}prt_dsmk_m`}
|
||||||
|
name={[field.name, "prt_dsmk_m"]}
|
||||||
|
>
|
||||||
|
<ReadOnlyFormItem type="currency" />
|
||||||
|
</Form.Item>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
span={2}
|
span={2}
|
||||||
@@ -108,7 +119,9 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
|||||||
labelCol={{ span: 0 }}
|
labelCol={{ span: 0 }}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: !!job.joblines[index].act_price,
|
required:
|
||||||
|
!!job.joblines[index].act_price ||
|
||||||
|
!!job.joblines[index].prt_dsmk_m,
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -30,6 +31,7 @@ export function JobsExportAllButton({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [updateJob] = useMutation(UPDATE_JOBS);
|
const [updateJob] = useMutation(UPDATE_JOBS);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
|
const [cookies] = useCookies();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const handleQbxml = async () => {
|
const handleQbxml = async () => {
|
||||||
@@ -37,10 +39,13 @@ export function JobsExportAllButton({
|
|||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
PartnerResponse = await axios.post(
|
||||||
withCredentials: true,
|
`/qbo/receivables`,
|
||||||
jobIds: jobIds,
|
{
|
||||||
});
|
jobIds: jobIds,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let QbXmlResponse;
|
let QbXmlResponse;
|
||||||
try {
|
try {
|
||||||
@@ -173,8 +178,12 @@ export function JobsExportAllButton({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleQbxml}
|
onClick={handleQbxml}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={
|
||||||
type="dashed"
|
disabled ||
|
||||||
|
(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
!cookies.qbo_realmId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -32,6 +33,7 @@ export function PayableExportAll({
|
|||||||
const [updateBill] = useMutation(UPDATE_BILLS);
|
const [updateBill] = useMutation(UPDATE_BILLS);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
|
const [cookies] = useCookies();
|
||||||
|
|
||||||
const handleQbxml = async () => {
|
const handleQbxml = async () => {
|
||||||
logImEXEvent("accounting_payables_export_all");
|
logImEXEvent("accounting_payables_export_all");
|
||||||
@@ -40,10 +42,13 @@ export function PayableExportAll({
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!!loadingCallback) loadingCallback(true);
|
if (!!loadingCallback) loadingCallback(true);
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
PartnerResponse = await axios.post(
|
||||||
withCredentials: true,
|
`/qbo/receivables`,
|
||||||
bills: billids,
|
{
|
||||||
});
|
bills: billids,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let QbXmlResponse;
|
let QbXmlResponse;
|
||||||
try {
|
try {
|
||||||
@@ -172,8 +177,12 @@ export function PayableExportAll({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleQbxml}
|
onClick={handleQbxml}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={
|
||||||
type="dashed"
|
disabled ||
|
||||||
|
(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
!cookies.qbo_realmId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from "../../redux/user/user.selectors";
|
} from "../../redux/user/user.selectors";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -31,6 +32,7 @@ export function PayableExportButton({
|
|||||||
const [updateBill] = useMutation(UPDATE_BILLS);
|
const [updateBill] = useMutation(UPDATE_BILLS);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
|
const [cookies] = useCookies();
|
||||||
|
|
||||||
const handleQbxml = async () => {
|
const handleQbxml = async () => {
|
||||||
logImEXEvent("accounting_export_payable");
|
logImEXEvent("accounting_export_payable");
|
||||||
@@ -41,10 +43,13 @@ export function PayableExportButton({
|
|||||||
//Check if it's a QBO Setup.
|
//Check if it's a QBO Setup.
|
||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
PartnerResponse = await axios.post(`/qbo/payables`, {
|
PartnerResponse = await axios.post(
|
||||||
withCredentials: true,
|
`/qbo/payables`,
|
||||||
bills: [billId],
|
{
|
||||||
});
|
bills: [billId],
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
//Default is QBD
|
//Default is QBD
|
||||||
|
|
||||||
@@ -174,8 +179,12 @@ export function PayableExportButton({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleQbxml}
|
onClick={handleQbxml}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={
|
||||||
type="dashed"
|
disabled ||
|
||||||
|
(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
!cookies.qbo_realmId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
@@ -30,58 +32,71 @@ export function PaymentExportButton({
|
|||||||
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
|
const [cookies] = useCookies();
|
||||||
|
|
||||||
const handleQbxml = async () => {
|
const handleQbxml = async () => {
|
||||||
logImEXEvent("accounting_payment_export");
|
logImEXEvent("accounting_payment_export");
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!!loadingCallback) loadingCallback(true);
|
//Check if it's a QBO Setup.
|
||||||
|
|
||||||
let QbXmlResponse;
|
|
||||||
try {
|
|
||||||
QbXmlResponse = await axios.post(
|
|
||||||
"/accounting/qbxml/payments",
|
|
||||||
{ payments: [paymentId] },
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
);
|
|
||||||
console.log("handle -> XML", QbXmlResponse);
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error getting QBXML from Server.", error);
|
|
||||||
notification["error"]({
|
|
||||||
message: t("payments.errors.exporting", {
|
|
||||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (loadingCallback) loadingCallback(false);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
try {
|
|
||||||
PartnerResponse = await axios.post(
|
PartnerResponse = await axios.post(
|
||||||
"http://localhost:1337/qb/",
|
`/qbo/payments`,
|
||||||
//"http://609feaeae986.ngrok.io/qb/",
|
{
|
||||||
QbXmlResponse.data
|
payments: [paymentId],
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} else {
|
||||||
console.log("Error connecting to quickbooks or partner.", error);
|
//Default is QBD
|
||||||
notification["error"]({
|
|
||||||
message: t("payments.errors.exporting-partner"),
|
if (!!loadingCallback) loadingCallback(true);
|
||||||
});
|
|
||||||
if (!!loadingCallback) loadingCallback(false);
|
let QbXmlResponse;
|
||||||
setLoading(false);
|
try {
|
||||||
return;
|
QbXmlResponse = await axios.post(
|
||||||
|
"/accounting/qbxml/payments",
|
||||||
|
{ payments: [paymentId] },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log("handle -> XML", QbXmlResponse);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error getting QBXML from Server.", error);
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting", {
|
||||||
|
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (loadingCallback) loadingCallback(false);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PartnerResponse = await axios.post(
|
||||||
|
"http://localhost:1337/qb/",
|
||||||
|
QbXmlResponse.data
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error connecting to quickbooks or partner.", error);
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting-partner"),
|
||||||
|
});
|
||||||
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
|
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
|
||||||
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
||||||
|
const successfulTransactions = PartnerResponse.data.filter(
|
||||||
|
(r) => r.success
|
||||||
|
);
|
||||||
if (failedTransactions.length > 0) {
|
if (failedTransactions.length > 0) {
|
||||||
//Uh oh. At least one was no good.
|
//Uh oh. At least one was no good.
|
||||||
failedTransactions.map((ft) =>
|
failedTransactions.map((ft) =>
|
||||||
@@ -123,7 +138,14 @@ export function PaymentExportButton({
|
|||||||
|
|
||||||
const paymentUpdateResponse = await updatePayment({
|
const paymentUpdateResponse = await updatePayment({
|
||||||
variables: {
|
variables: {
|
||||||
paymentIdList: [paymentId],
|
paymentIdList: successfulTransactions.map(
|
||||||
|
(st) =>
|
||||||
|
st[
|
||||||
|
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
||||||
|
? "paymentid"
|
||||||
|
: "id"
|
||||||
|
]
|
||||||
|
),
|
||||||
payment: {
|
payment: {
|
||||||
exportedat: new Date(),
|
exportedat: new Date(),
|
||||||
},
|
},
|
||||||
@@ -158,8 +180,12 @@ export function PaymentExportButton({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleQbxml}
|
onClick={handleQbxml}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={
|
||||||
type="dashed"
|
disabled ||
|
||||||
|
(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
!cookies.qbo_realmId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
||||||
|
import { useCookies } from "react-cookie";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectBodyshop,
|
selectBodyshop,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
@@ -29,46 +31,58 @@ export function PaymentsExportAllButton({
|
|||||||
const [updatePayments] = useMutation(UPDATE_PAYMENTS);
|
const [updatePayments] = useMutation(UPDATE_PAYMENTS);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
|
const [cookies] = useCookies();
|
||||||
|
|
||||||
const handleQbxml = async () => {
|
const handleQbxml = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
if (!!loadingCallback) loadingCallback(true);
|
if (!!loadingCallback) loadingCallback(true);
|
||||||
|
|
||||||
let QbXmlResponse;
|
|
||||||
try {
|
|
||||||
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
|
|
||||||
payments: paymentIds,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.log("Error getting QBXML from Server.", error);
|
|
||||||
notification["error"]({
|
|
||||||
message: t("payments.errors.exporting", {
|
|
||||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
if (loadingCallback) loadingCallback(false);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
|
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||||
try {
|
|
||||||
PartnerResponse = await axios.post(
|
PartnerResponse = await axios.post(
|
||||||
"http://localhost:1337/qb/",
|
`/qbo/payments`,
|
||||||
QbXmlResponse.data
|
{
|
||||||
|
payments: paymentIds,
|
||||||
|
},
|
||||||
|
{ withCredentials: true }
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} else {
|
||||||
console.log("Error connecting to quickbooks or partner.", error);
|
let QbXmlResponse;
|
||||||
notification["error"]({
|
try {
|
||||||
message: t("payments.errors.exporting-partner"),
|
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
|
||||||
});
|
payments: paymentIds,
|
||||||
if (!!loadingCallback) loadingCallback(false);
|
});
|
||||||
setLoading(false);
|
} catch (error) {
|
||||||
return;
|
console.log("Error getting QBXML from Server.", error);
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting", {
|
||||||
|
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (loadingCallback) loadingCallback(false);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
PartnerResponse = await axios.post(
|
||||||
|
"http://localhost:1337/qb/",
|
||||||
|
QbXmlResponse.data
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Error connecting to quickbooks or partner.", error);
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting-partner"),
|
||||||
|
});
|
||||||
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupedData = _.groupBy(PartnerResponse.data, "id");
|
const groupedData = _.groupBy(
|
||||||
|
PartnerResponse.data,
|
||||||
|
bodyshop.accountingconfig.qbo ? "paymentid" : "id"
|
||||||
|
);
|
||||||
const proms = [];
|
const proms = [];
|
||||||
Object.keys(groupedData).forEach((key) => {
|
Object.keys(groupedData).forEach((key) => {
|
||||||
proms.push(
|
proms.push(
|
||||||
@@ -147,8 +161,12 @@ export function PaymentsExportAllButton({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleQbxml}
|
onClick={handleQbxml}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={disabled}
|
disabled={
|
||||||
type="dashed"
|
disabled ||
|
||||||
|
(bodyshop.accountingconfig &&
|
||||||
|
bodyshop.accountingconfig.qbo &&
|
||||||
|
!cookies.qbo_realmId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -6,9 +6,11 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
technician: selectTechnician,
|
technician: selectTechnician,
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
@@ -21,6 +23,7 @@ export default connect(
|
|||||||
export function ProductionColumnsComponent({
|
export function ProductionColumnsComponent({
|
||||||
columnState,
|
columnState,
|
||||||
technician,
|
technician,
|
||||||
|
bodyshop,
|
||||||
tableState,
|
tableState,
|
||||||
}) {
|
}) {
|
||||||
const [columns, setColumns] = columnState;
|
const [columns, setColumns] = columnState;
|
||||||
@@ -29,9 +32,11 @@ export function ProductionColumnsComponent({
|
|||||||
const handleAdd = (e) => {
|
const handleAdd = (e) => {
|
||||||
setColumns([
|
setColumns([
|
||||||
...columns,
|
...columns,
|
||||||
...dataSource({ technician, state: tableState }).filter(
|
...dataSource({
|
||||||
(i) => i.key === e.key
|
technician,
|
||||||
),
|
state: tableState,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
}).filter((i) => i.key === e.key),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -39,7 +44,11 @@ export function ProductionColumnsComponent({
|
|||||||
|
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu onClick={handleAdd}>
|
<Menu onClick={handleAdd}>
|
||||||
{dataSource({ technician, state: tableState })
|
{dataSource({
|
||||||
|
technician,
|
||||||
|
state: tableState,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
})
|
||||||
.filter((i) => !columnKeys.includes(i.key))
|
.filter((i) => !columnKeys.includes(i.key))
|
||||||
.map((item) => (
|
.map((item) => (
|
||||||
<Menu.Item key={item.key}>{item.title}</Menu.Item>
|
<Menu.Item key={item.key}>{item.title}</Menu.Item>
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ export default function QboAuthorizeComponent() {
|
|||||||
setCookie("qbo_realmId", realmId, {
|
setCookie("qbo_realmId", realmId, {
|
||||||
path: "/",
|
path: "/",
|
||||||
expires,
|
expires,
|
||||||
|
|
||||||
|
...(process.env.NODE_ENV !== "development"
|
||||||
|
? { domain: `.${window.location.host}` }
|
||||||
|
: {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
history.push({ pathname: `/manage/accounting/receivables` });
|
history.push({ pathname: `/manage/accounting/receivables` });
|
||||||
|
|||||||
@@ -1769,7 +1769,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
|||||||
actual_in
|
actual_in
|
||||||
kmin
|
kmin
|
||||||
kmout
|
kmout
|
||||||
joblines(where: { removed: { _eq: false } }) {
|
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||||
id
|
id
|
||||||
removed
|
removed
|
||||||
tax_part
|
tax_part
|
||||||
|
|||||||
@@ -1053,7 +1053,8 @@
|
|||||||
},
|
},
|
||||||
"profitcenter_labor": "Profit Center: Labor",
|
"profitcenter_labor": "Profit Center: Labor",
|
||||||
"profitcenter_part": "Profit Center: Part",
|
"profitcenter_part": "Profit Center: Part",
|
||||||
"prt_dsmk_p": "Line Markup %",
|
"prt_dsmk_m": "Line Discount/Markup $",
|
||||||
|
"prt_dsmk_p": "Line Discount/Markup %",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"tax_part": "Tax Part",
|
"tax_part": "Tax Part",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
|
|||||||
@@ -1053,6 +1053,7 @@
|
|||||||
},
|
},
|
||||||
"profitcenter_labor": "",
|
"profitcenter_labor": "",
|
||||||
"profitcenter_part": "",
|
"profitcenter_part": "",
|
||||||
|
"prt_dsmk_m": "",
|
||||||
"prt_dsmk_p": "",
|
"prt_dsmk_p": "",
|
||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"tax_part": "",
|
"tax_part": "",
|
||||||
|
|||||||
@@ -1053,6 +1053,7 @@
|
|||||||
},
|
},
|
||||||
"profitcenter_labor": "",
|
"profitcenter_labor": "",
|
||||||
"profitcenter_part": "",
|
"profitcenter_part": "",
|
||||||
|
"prt_dsmk_m": "",
|
||||||
"prt_dsmk_p": "",
|
"prt_dsmk_p": "",
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"tax_part": "",
|
"tax_part": "",
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ export function alphaSort(a, b) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function dateSort(a, b) {
|
export function dateSort(a, b) {
|
||||||
return new Date(b) - new Date(a);
|
return new Date(a) - new Date(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function statusSort(a, b, statusList) {
|
export function statusSort(a, b, statusList) {
|
||||||
return (
|
return (
|
||||||
statusList.findIndex((x) => x === a) > statusList.findIndex((x) => x === b)
|
statusList.findIndex((x) => x === a) - statusList.findIndex((x) => x === b)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
47
logs/oAuthClient-log.log
Normal file
47
logs/oAuthClient-log.log
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
2021-10-14T18:14:19.821Z info: Create Token response is : {
|
||||||
|
"token": {
|
||||||
|
"realmId": "4620816365180784050",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"access_token": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..dRa330Va1FympcUMjnlbkw.W1_-IFux2NF5wqk48iEyYCPEToNm0KA5OQPOOk0kB75xOQrl-ewYSv3ZPmM56c4p8rzlNUr1V3lF3fcpKcZWSgShNX-dYAQ5WfhaBVz27wI0eXE3RUNbsuUwWc4pKtdU3pFlI2Bz94m9zVY8DKlw1Pm4CFGhqf41IqCcgn6VxYE-uRC2L5VLXyddEGwJoTpAWeA0JEtUc0DoTMVPFnOb6zUEgP5MGj5w81eHludNfl9QxGcitwpqrAHk1UDK0GfgvROZDmfrgvZ0r0SQ9aiI1_XWisZjnqzzcr4fMzQq85CoujnYfgAzi4-En3YgP0D6q_OnUSY5NjFzUsU_4ikganeFx5WgRv9jJDDwcDsSnkoJFSdgEoj_oWwTtYr-9RXjKBdothgXySicLEA5J_PdCGXZJ_rxQ_hW6RndVLMSrlADCWPpKGhT_PV96UB-EO_m0kFvnCOqAuvAcSRC46OmJh9sedTqC5pMHPgOwnosQzHdR-9jKxEivfw8A49IucMhl9pJzPDKPVSdw-e9vlt4OMo_fBcYHb14QxoxJNPZQV6KUe4aSqxjjK0-bFOOk33sNGr-IHZgor1FZyYDK5OBgROLQx98Sm6suJ-GVuG_s0O7VwsqXRSYY9UpWcg4xKfLG6WXFSANa2kc41G4zAsB-4eQJS654t3ycl2Qh50VNKMrPusRhFpMJVvLn0zEiWTjZKswk559Q_1-NpSxUbquatbERDKu9keY7nL5mjWEJUDl8o7e7GpzY05KATWvjjz39OCYUjVhT_kMEho9td7TUjltakgmx-Go8VDbBYqyrPaPdasZfS59w1QhZUJPLFPmRA0H1jxc8qu841eHDBi4kw.vsfZZv3p2rLJWnM9SWbJzg",
|
||||||
|
"refresh_token": "AB11642961656aZhy8GyGhOImeMq6hd5q3j8S9FJGT4MppS92Y",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"x_refresh_token_expires_in": 8726400,
|
||||||
|
"id_token": "eyJraWQiOiJPUElDUFJEMDkxODIwMTQiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI1ODYwNzMxZC0yZmQ1LTQ3YTMtOTgxNi0wYzE5N2NkZDk1NjIiLCJhdWQiOlsiQUJnY01pUkZvNmdWZWZtOFBCZkZwZkxQdzFoUzZCekluMFduUlZLR1A5R05SWm1neHQiXSwicmVhbG1pZCI6IjQ2MjA4MTYzNjUxODA3ODQwNTAiLCJhdXRoX3RpbWUiOjE2MzQyMzUyNTAsImlzcyI6Imh0dHBzOlwvXC9vYXV0aC5wbGF0Zm9ybS5pbnR1aXQuY29tXC9vcFwvdjEiLCJleHAiOjE2MzQyMzg4NTYsImlhdCI6MTYzNDIzNTI1Nn0.ZMNPsFfYqnyilp0fFrW5RvMht6Eb1MFTY2X_zx3dcuoH2sAK5EYaKgtm9EZq5VeH_mQ4qGQLGgl70v3v_2I0ElmFOZeSGUO1Q5UcXxaK5-A_8Wed5qr_qpQWGWpc2pkWNM1Q2hQfDVfX9J45Ob2qumYlJ49V_p5Sm6WuqyIrfTQ",
|
||||||
|
"latency": 60000,
|
||||||
|
"createdAt": 1634235259820,
|
||||||
|
"state": "patrick@imex.dev"
|
||||||
|
},
|
||||||
|
"response": {
|
||||||
|
"url": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
|
||||||
|
"headers": {
|
||||||
|
"date": "Thu, 14 Oct 2021 18:14:16 GMT",
|
||||||
|
"content-type": "application/json;charset=utf-8",
|
||||||
|
"content-length": "1684",
|
||||||
|
"connection": "close",
|
||||||
|
"intuit_tid": "1-61687378-07987874613592c04a0e3709",
|
||||||
|
"x-spanid": "ebcfa726-ad39-401b-8fb5-ad018bdce8a1",
|
||||||
|
"x-amzn-trace-id": "Root=1-61687378-07987874613592c04a0e3709",
|
||||||
|
"x-content-type-options": "nosniff",
|
||||||
|
"server": "envoy",
|
||||||
|
"cache-control": "no-cache, no-store",
|
||||||
|
"pragma": "no-cache",
|
||||||
|
"x-frame-options": "SAMEORIGIN",
|
||||||
|
"x-xss-protection": "1; mode=block",
|
||||||
|
"x-envoy-upstream-service-time": "76",
|
||||||
|
"strict-transport-security": "max-age=31536000"
|
||||||
|
},
|
||||||
|
"body": "{\"x_refresh_token_expires_in\":8726400,\"refresh_token\":\"AB11642961656aZhy8GyGhOImeMq6hd5q3j8S9FJGT4MppS92Y\",\"access_token\":\"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..dRa330Va1FympcUMjnlbkw.W1_-IFux2NF5wqk48iEyYCPEToNm0KA5OQPOOk0kB75xOQrl-ewYSv3ZPmM56c4p8rzlNUr1V3lF3fcpKcZWSgShNX-dYAQ5WfhaBVz27wI0eXE3RUNbsuUwWc4pKtdU3pFlI2Bz94m9zVY8DKlw1Pm4CFGhqf41IqCcgn6VxYE-uRC2L5VLXyddEGwJoTpAWeA0JEtUc0DoTMVPFnOb6zUEgP5MGj5w81eHludNfl9QxGcitwpqrAHk1UDK0GfgvROZDmfrgvZ0r0SQ9aiI1_XWisZjnqzzcr4fMzQq85CoujnYfgAzi4-En3YgP0D6q_OnUSY5NjFzUsU_4ikganeFx5WgRv9jJDDwcDsSnkoJFSdgEoj_oWwTtYr-9RXjKBdothgXySicLEA5J_PdCGXZJ_rxQ_hW6RndVLMSrlADCWPpKGhT_PV96UB-EO_m0kFvnCOqAuvAcSRC46OmJh9sedTqC5pMHPgOwnosQzHdR-9jKxEivfw8A49IucMhl9pJzPDKPVSdw-e9vlt4OMo_fBcYHb14QxoxJNPZQV6KUe4aSqxjjK0-bFOOk33sNGr-IHZgor1FZyYDK5OBgROLQx98Sm6suJ-GVuG_s0O7VwsqXRSYY9UpWcg4xKfLG6WXFSANa2kc41G4zAsB-4eQJS654t3ycl2Qh50VNKMrPusRhFpMJVvLn0zEiWTjZKswk559Q_1-NpSxUbquatbERDKu9keY7nL5mjWEJUDl8o7e7GpzY05KATWvjjz39OCYUjVhT_kMEho9td7TUjltakgmx-Go8VDbBYqyrPaPdasZfS59w1QhZUJPLFPmRA0H1jxc8qu841eHDBi4kw.vsfZZv3p2rLJWnM9SWbJzg\",\"id_token\":\"eyJraWQiOiJPUElDUFJEMDkxODIwMTQiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI1ODYwNzMxZC0yZmQ1LTQ3YTMtOTgxNi0wYzE5N2NkZDk1NjIiLCJhdWQiOlsiQUJnY01pUkZvNmdWZWZtOFBCZkZwZkxQdzFoUzZCekluMFduUlZLR1A5R05SWm1neHQiXSwicmVhbG1pZCI6IjQ2MjA4MTYzNjUxODA3ODQwNTAiLCJhdXRoX3RpbWUiOjE2MzQyMzUyNTAsImlzcyI6Imh0dHBzOlwvXC9vYXV0aC5wbGF0Zm9ybS5pbnR1aXQuY29tXC9vcFwvdjEiLCJleHAiOjE2MzQyMzg4NTYsImlhdCI6MTYzNDIzNTI1Nn0.ZMNPsFfYqnyilp0fFrW5RvMht6Eb1MFTY2X_zx3dcuoH2sAK5EYaKgtm9EZq5VeH_mQ4qGQLGgl70v3v_2I0ElmFOZeSGUO1Q5UcXxaK5-A_8Wed5qr_qpQWGWpc2pkWNM1Q2hQfDVfX9J45Ob2qumYlJ49V_p5Sm6WuqyIrfTQ\",\"token_type\":\"bearer\",\"expires_in\":3600}",
|
||||||
|
"status": 200,
|
||||||
|
"statusText": "OK"
|
||||||
|
},
|
||||||
|
"body": "{\"x_refresh_token_expires_in\":8726400,\"refresh_token\":\"AB11642961656aZhy8GyGhOImeMq6hd5q3j8S9FJGT4MppS92Y\",\"access_token\":\"eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..dRa330Va1FympcUMjnlbkw.W1_-IFux2NF5wqk48iEyYCPEToNm0KA5OQPOOk0kB75xOQrl-ewYSv3ZPmM56c4p8rzlNUr1V3lF3fcpKcZWSgShNX-dYAQ5WfhaBVz27wI0eXE3RUNbsuUwWc4pKtdU3pFlI2Bz94m9zVY8DKlw1Pm4CFGhqf41IqCcgn6VxYE-uRC2L5VLXyddEGwJoTpAWeA0JEtUc0DoTMVPFnOb6zUEgP5MGj5w81eHludNfl9QxGcitwpqrAHk1UDK0GfgvROZDmfrgvZ0r0SQ9aiI1_XWisZjnqzzcr4fMzQq85CoujnYfgAzi4-En3YgP0D6q_OnUSY5NjFzUsU_4ikganeFx5WgRv9jJDDwcDsSnkoJFSdgEoj_oWwTtYr-9RXjKBdothgXySicLEA5J_PdCGXZJ_rxQ_hW6RndVLMSrlADCWPpKGhT_PV96UB-EO_m0kFvnCOqAuvAcSRC46OmJh9sedTqC5pMHPgOwnosQzHdR-9jKxEivfw8A49IucMhl9pJzPDKPVSdw-e9vlt4OMo_fBcYHb14QxoxJNPZQV6KUe4aSqxjjK0-bFOOk33sNGr-IHZgor1FZyYDK5OBgROLQx98Sm6suJ-GVuG_s0O7VwsqXRSYY9UpWcg4xKfLG6WXFSANa2kc41G4zAsB-4eQJS654t3ycl2Qh50VNKMrPusRhFpMJVvLn0zEiWTjZKswk559Q_1-NpSxUbquatbERDKu9keY7nL5mjWEJUDl8o7e7GpzY05KATWvjjz39OCYUjVhT_kMEho9td7TUjltakgmx-Go8VDbBYqyrPaPdasZfS59w1QhZUJPLFPmRA0H1jxc8qu841eHDBi4kw.vsfZZv3p2rLJWnM9SWbJzg\",\"id_token\":\"eyJraWQiOiJPUElDUFJEMDkxODIwMTQiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI1ODYwNzMxZC0yZmQ1LTQ3YTMtOTgxNi0wYzE5N2NkZDk1NjIiLCJhdWQiOlsiQUJnY01pUkZvNmdWZWZtOFBCZkZwZkxQdzFoUzZCekluMFduUlZLR1A5R05SWm1neHQiXSwicmVhbG1pZCI6IjQ2MjA4MTYzNjUxODA3ODQwNTAiLCJhdXRoX3RpbWUiOjE2MzQyMzUyNTAsImlzcyI6Imh0dHBzOlwvXC9vYXV0aC5wbGF0Zm9ybS5pbnR1aXQuY29tXC9vcFwvdjEiLCJleHAiOjE2MzQyMzg4NTYsImlhdCI6MTYzNDIzNTI1Nn0.ZMNPsFfYqnyilp0fFrW5RvMht6Eb1MFTY2X_zx3dcuoH2sAK5EYaKgtm9EZq5VeH_mQ4qGQLGgl70v3v_2I0ElmFOZeSGUO1Q5UcXxaK5-A_8Wed5qr_qpQWGWpc2pkWNM1Q2hQfDVfX9J45Ob2qumYlJ49V_p5Sm6WuqyIrfTQ\",\"token_type\":\"bearer\",\"expires_in\":3600}",
|
||||||
|
"json": {
|
||||||
|
"x_refresh_token_expires_in": 8726400,
|
||||||
|
"refresh_token": "AB11642961656aZhy8GyGhOImeMq6hd5q3j8S9FJGT4MppS92Y",
|
||||||
|
"access_token": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..dRa330Va1FympcUMjnlbkw.W1_-IFux2NF5wqk48iEyYCPEToNm0KA5OQPOOk0kB75xOQrl-ewYSv3ZPmM56c4p8rzlNUr1V3lF3fcpKcZWSgShNX-dYAQ5WfhaBVz27wI0eXE3RUNbsuUwWc4pKtdU3pFlI2Bz94m9zVY8DKlw1Pm4CFGhqf41IqCcgn6VxYE-uRC2L5VLXyddEGwJoTpAWeA0JEtUc0DoTMVPFnOb6zUEgP5MGj5w81eHludNfl9QxGcitwpqrAHk1UDK0GfgvROZDmfrgvZ0r0SQ9aiI1_XWisZjnqzzcr4fMzQq85CoujnYfgAzi4-En3YgP0D6q_OnUSY5NjFzUsU_4ikganeFx5WgRv9jJDDwcDsSnkoJFSdgEoj_oWwTtYr-9RXjKBdothgXySicLEA5J_PdCGXZJ_rxQ_hW6RndVLMSrlADCWPpKGhT_PV96UB-EO_m0kFvnCOqAuvAcSRC46OmJh9sedTqC5pMHPgOwnosQzHdR-9jKxEivfw8A49IucMhl9pJzPDKPVSdw-e9vlt4OMo_fBcYHb14QxoxJNPZQV6KUe4aSqxjjK0-bFOOk33sNGr-IHZgor1FZyYDK5OBgROLQx98Sm6suJ-GVuG_s0O7VwsqXRSYY9UpWcg4xKfLG6WXFSANa2kc41G4zAsB-4eQJS654t3ycl2Qh50VNKMrPusRhFpMJVvLn0zEiWTjZKswk559Q_1-NpSxUbquatbERDKu9keY7nL5mjWEJUDl8o7e7GpzY05KATWvjjz39OCYUjVhT_kMEho9td7TUjltakgmx-Go8VDbBYqyrPaPdasZfS59w1QhZUJPLFPmRA0H1jxc8qu841eHDBi4kw.vsfZZv3p2rLJWnM9SWbJzg",
|
||||||
|
"id_token": "eyJraWQiOiJPUElDUFJEMDkxODIwMTQiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI1ODYwNzMxZC0yZmQ1LTQ3YTMtOTgxNi0wYzE5N2NkZDk1NjIiLCJhdWQiOlsiQUJnY01pUkZvNmdWZWZtOFBCZkZwZkxQdzFoUzZCekluMFduUlZLR1A5R05SWm1neHQiXSwicmVhbG1pZCI6IjQ2MjA4MTYzNjUxODA3ODQwNTAiLCJhdXRoX3RpbWUiOjE2MzQyMzUyNTAsImlzcyI6Imh0dHBzOlwvXC9vYXV0aC5wbGF0Zm9ybS5pbnR1aXQuY29tXC9vcFwvdjEiLCJleHAiOjE2MzQyMzg4NTYsImlhdCI6MTYzNDIzNTI1Nn0.ZMNPsFfYqnyilp0fFrW5RvMht6Eb1MFTY2X_zx3dcuoH2sAK5EYaKgtm9EZq5VeH_mQ4qGQLGgl70v3v_2I0ElmFOZeSGUO1Q5UcXxaK5-A_8Wed5qr_qpQWGWpc2pkWNM1Q2hQfDVfX9J45Ob2qumYlJ49V_p5Sm6WuqyIrfTQ",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
},
|
||||||
|
"intuit_tid": "1-61687378-07987874613592c04a0e3709"
|
||||||
|
}
|
||||||
12
server.js
12
server.js
@@ -45,7 +45,16 @@ app.use(cookieParser());
|
|||||||
app.use(bodyParser.json({ limit: "50mb" }));
|
app.use(bodyParser.json({ limit: "50mb" }));
|
||||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
||||||
//app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
//app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
||||||
app.use(cors());
|
app.use(
|
||||||
|
cors({
|
||||||
|
credentials: true,
|
||||||
|
origin: [
|
||||||
|
"https://test.imex.online",
|
||||||
|
"http://localhost:3000",
|
||||||
|
"https://imex.online",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
//Email Based Paths.
|
//Email Based Paths.
|
||||||
var sendEmail = require("./server/email/sendemail.js");
|
var sendEmail = require("./server/email/sendemail.js");
|
||||||
@@ -150,6 +159,7 @@ app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize);
|
|||||||
app.get("/qbo/callback", qbo.callback);
|
app.get("/qbo/callback", qbo.callback);
|
||||||
app.post("/qbo/receivables", fb.validateFirebaseIdToken, qbo.receivables);
|
app.post("/qbo/receivables", fb.validateFirebaseIdToken, qbo.receivables);
|
||||||
app.post("/qbo/payables", fb.validateFirebaseIdToken, qbo.payables);
|
app.post("/qbo/payables", fb.validateFirebaseIdToken, qbo.payables);
|
||||||
|
app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments);
|
||||||
|
|
||||||
var data = require("./server/data/data");
|
var data = require("./server/data/data");
|
||||||
app.post("/data/ah", data.autohouse);
|
app.post("/data/ah", data.autohouse);
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ exports.default = function ({
|
|||||||
hasMashLine = true;
|
hasMashLine = true;
|
||||||
}
|
}
|
||||||
//Parts Lines Mappings.
|
//Parts Lines Mappings.
|
||||||
if (jobline.profitcenter_part && jobline.act_price) {
|
if (jobline.profitcenter_part) {
|
||||||
let DineroAmount = Dinero({
|
let DineroAmount = Dinero({
|
||||||
amount: Math.round(jobline.act_price * 100),
|
amount: Math.round((jobline.act_price || 0) * 100),
|
||||||
}).multiply(jobline.part_qty || 1);
|
}).multiply(jobline.part_qty || 1);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -315,8 +315,12 @@ exports.default = function ({
|
|||||||
if (qbo) {
|
if (qbo) {
|
||||||
Object.keys(invoiceLineHash).forEach((key) => {
|
Object.keys(invoiceLineHash).forEach((key) => {
|
||||||
Object.keys(invoiceLineHash[key]).forEach((key2) => {
|
Object.keys(invoiceLineHash[key]).forEach((key2) => {
|
||||||
|
const account = responsibilityCenters.profits.find(
|
||||||
|
(p) => p.name === key
|
||||||
|
);
|
||||||
InvoiceLineAdd.push({
|
InvoiceLineAdd.push({
|
||||||
...invoiceLineHash[key][key2],
|
...invoiceLineHash[key][key2],
|
||||||
|
...(account ? { Description: account.accountdesc } : {}),
|
||||||
Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat),
|
Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const OAuthClient = require("intuit-oauth");
|
|||||||
const client = require("../../graphql-client/graphql-client").client;
|
const client = require("../../graphql-client/graphql-client").client;
|
||||||
const queries = require("../../graphql-client/queries");
|
const queries = require("../../graphql-client/queries");
|
||||||
const queryString = require("query-string");
|
const queryString = require("query-string");
|
||||||
|
|
||||||
const oauthClient = new OAuthClient({
|
const oauthClient = new OAuthClient({
|
||||||
clientId: process.env.QBO_CLIENT_ID,
|
clientId: process.env.QBO_CLIENT_ID,
|
||||||
clientSecret: process.env.QBO_SECRET,
|
clientSecret: process.env.QBO_SECRET,
|
||||||
@@ -18,6 +19,16 @@ const oauthClient = new OAuthClient({
|
|||||||
logging: true,
|
logging: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let url;
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === "production") {
|
||||||
|
url = `https://imex.online`;
|
||||||
|
} else if (process.env.NODE_ENV === "test") {
|
||||||
|
url = `https://test.imex.online`;
|
||||||
|
} else {
|
||||||
|
url = `http://localhost:3000`;
|
||||||
|
}
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const params = queryString.parse(req.url.split("?").reverse()[0]);
|
const params = queryString.parse(req.url.split("?").reverse()[0]);
|
||||||
try {
|
try {
|
||||||
@@ -28,7 +39,7 @@ exports.default = async (req, res) => {
|
|||||||
error: authResponse.json,
|
error: authResponse.json,
|
||||||
});
|
});
|
||||||
res.redirect(
|
res.redirect(
|
||||||
`http://localhost:3000/manage/accounting/qbo?error=${encodeURIComponent(
|
`${url}/manage/accounting/qbo?error=${encodeURIComponent(
|
||||||
JSON.stringify(authResponse.json)
|
JSON.stringify(authResponse.json)
|
||||||
)}`
|
)}`
|
||||||
);
|
);
|
||||||
@@ -46,9 +57,7 @@ exports.default = async (req, res) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
res.redirect(
|
res.redirect(
|
||||||
`http://localhost:3000/manage/accounting/qbo?${queryString.stringify(
|
`${url}/manage/accounting/qbo?${queryString.stringify(params)}`
|
||||||
params
|
|
||||||
)}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -72,12 +72,15 @@ exports.default = async (req, res) => {
|
|||||||
bill,
|
bill,
|
||||||
vendorRecord
|
vendorRecord
|
||||||
);
|
);
|
||||||
|
|
||||||
ret.push({ billid: bill.id, success: true });
|
ret.push({ billid: bill.id, success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ret.push({
|
ret.push({
|
||||||
billid: bill.id,
|
billid: bill.id,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: error.message,
|
errorMessage:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +115,9 @@ async function QueryVendorRecord(oauthClient, req, bill) {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||||
error,
|
error:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
method: "QueryVendorRecord",
|
method: "QueryVendorRecord",
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
@@ -135,7 +140,9 @@ async function InsertVendorRecord(oauthClient, req, bill) {
|
|||||||
return result && result.Vendor;
|
return result && result.Vendor;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||||
error,
|
error:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
method: "InsertVendorRecord",
|
method: "InsertVendorRecord",
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
@@ -150,7 +157,7 @@ async function InsertBill(oauthClient, req, bill, vendor) {
|
|||||||
value: vendor.Id,
|
value: vendor.Id,
|
||||||
},
|
},
|
||||||
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
|
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
|
||||||
DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
|
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
|
||||||
DocNumber: bill.invoice_number,
|
DocNumber: bill.invoice_number,
|
||||||
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
|
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
|
||||||
|
|
||||||
@@ -164,13 +171,20 @@ async function InsertBill(oauthClient, req, bill, vendor) {
|
|||||||
bill.job.class,
|
bill.job.class,
|
||||||
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
|
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
|
||||||
classes,
|
classes,
|
||||||
taxCodes
|
taxCodes,
|
||||||
|
bill.job.bodyshop.md_responsibility_centers.costs
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
|
||||||
|
billQbo,
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
const result = await oauthClient.makeApiCall({
|
const result = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(req.cookies.qbo_realmId, "bill"),
|
url: urlBuilder(
|
||||||
|
req.cookies.qbo_realmId,
|
||||||
|
bill.is_credit_memo ? "vendorcredit" : "bill"
|
||||||
|
),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -181,7 +195,9 @@ async function InsertBill(oauthClient, req, bill, vendor) {
|
|||||||
return result && result.Bill;
|
return result && result.Bill;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||||
error,
|
error:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
method: "InsertBill",
|
method: "InsertBill",
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
@@ -204,10 +220,12 @@ const generateBillLine = (
|
|||||||
billLine,
|
billLine,
|
||||||
accounts,
|
accounts,
|
||||||
jobClass,
|
jobClass,
|
||||||
responsibilityCenters,
|
ioSalesTaxCodes,
|
||||||
classes,
|
classes,
|
||||||
taxCodes
|
taxCodes,
|
||||||
|
costCenters
|
||||||
) => {
|
) => {
|
||||||
|
const account = costCenters.find((c) => c.name === billLine.cost_center);
|
||||||
return {
|
return {
|
||||||
DetailType: "AccountBasedExpenseLineDetail",
|
DetailType: "AccountBasedExpenseLineDetail",
|
||||||
|
|
||||||
@@ -215,12 +233,10 @@ const generateBillLine = (
|
|||||||
...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}),
|
...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}),
|
||||||
TaxCodeRef: {
|
TaxCodeRef: {
|
||||||
value:
|
value:
|
||||||
taxCodes[
|
taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)],
|
||||||
findTaxCode(billLine.applicable_taxes, responsibilityCenters)
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
AccountRef: {
|
AccountRef: {
|
||||||
value: accounts[billLine.cost_center],
|
value: accounts[account.accountname],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -231,6 +247,7 @@ const generateBillLine = (
|
|||||||
.toFormat(DineroQbFormat),
|
.toFormat(DineroQbFormat),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
async function QueryMetaData(oauthClient, req) {
|
async function QueryMetaData(oauthClient, req) {
|
||||||
const accounts = await oauthClient.makeApiCall({
|
const accounts = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(
|
url: urlBuilder(
|
||||||
@@ -264,6 +281,7 @@ async function QueryMetaData(oauthClient, req) {
|
|||||||
|
|
||||||
taxCodes.json &&
|
taxCodes.json &&
|
||||||
taxCodes.json.QueryResponse &&
|
taxCodes.json.QueryResponse &&
|
||||||
|
taxCodes.json.QueryResponse.TaxCode &&
|
||||||
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
||||||
taxCodeMapping[t.Name] = t.Id;
|
taxCodeMapping[t.Name] = t.Id;
|
||||||
});
|
});
|
||||||
@@ -272,13 +290,15 @@ async function QueryMetaData(oauthClient, req) {
|
|||||||
|
|
||||||
accounts.json &&
|
accounts.json &&
|
||||||
accounts.json.QueryResponse &&
|
accounts.json.QueryResponse &&
|
||||||
|
accounts.json.QueryResponse.Account &&
|
||||||
accounts.json.QueryResponse.Account.forEach((t) => {
|
accounts.json.QueryResponse.Account.forEach((t) => {
|
||||||
accountMapping[t.Name] = t.Id;
|
accountMapping[t.FullyQualifiedName] = t.Id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const classMapping = {};
|
const classMapping = {};
|
||||||
classes.json &&
|
classes.json &&
|
||||||
classes.json.QueryResponse &&
|
classes.json.QueryResponse &&
|
||||||
|
classes.json.QueryResponse.Class &&
|
||||||
classes.json.QueryResponse.Class.forEach((t) => {
|
classes.json.QueryResponse.Class.forEach((t) => {
|
||||||
accountMapping[t.Name] = t.Id;
|
accountMapping[t.Name] = t.Id;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,281 @@
|
|||||||
|
const path = require("path");
|
||||||
|
require("dotenv").config({
|
||||||
|
path: path.resolve(
|
||||||
|
process.cwd(),
|
||||||
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
const logger = require("../../utils/logger");
|
||||||
|
const Dinero = require("dinero.js");
|
||||||
|
|
||||||
|
const apiGqlClient = require("../../graphql-client/graphql-client").client;
|
||||||
|
const queries = require("../../graphql-client/queries");
|
||||||
|
const {
|
||||||
|
refresh: refreshOauthToken,
|
||||||
|
setNewRefreshToken,
|
||||||
|
} = require("./qbo-callback");
|
||||||
|
const OAuthClient = require("intuit-oauth");
|
||||||
|
const moment = require("moment");
|
||||||
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||||
|
const {
|
||||||
|
QueryInsuranceCo,
|
||||||
|
InsertInsuranceCo,
|
||||||
|
InsertJob,
|
||||||
|
InsertOwner,
|
||||||
|
QueryJob,
|
||||||
|
QueryOwner,
|
||||||
|
} = require("../qbo/qbo-receivables");
|
||||||
|
const { urlBuilder } = require("./qbo");
|
||||||
|
const { DineroQbFormat } = require("../accounting-constants");
|
||||||
|
|
||||||
|
exports.default = async (req, res) => {
|
||||||
|
const oauthClient = new OAuthClient({
|
||||||
|
clientId: process.env.QBO_CLIENT_ID,
|
||||||
|
clientSecret: process.env.QBO_SECRET,
|
||||||
|
environment:
|
||||||
|
process.env.NODE_ENV === "production" ? "production" : "sandbox",
|
||||||
|
redirectUri: process.env.QBO_REDIRECT_URI,
|
||||||
|
logging: true,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
//Fetch the API Access Tokens & Set them for the session.
|
||||||
|
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
||||||
|
email: req.user.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
oauthClient.setToken(response.associations[0].qbo_auth);
|
||||||
|
|
||||||
|
await refreshOauthToken(oauthClient, req);
|
||||||
|
|
||||||
|
const BearerToken = req.headers.authorization;
|
||||||
|
const { payments: paymentsToQuery } = req.body;
|
||||||
|
//Query Job Info
|
||||||
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
|
headers: {
|
||||||
|
Authorization: BearerToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
||||||
|
const result = await client
|
||||||
|
.setHeaders({ Authorization: BearerToken })
|
||||||
|
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||||
|
payments: paymentsToQuery,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { payments, bodyshops } = result;
|
||||||
|
const bodyshop = bodyshops[0];
|
||||||
|
|
||||||
|
const ret = [];
|
||||||
|
|
||||||
|
for (const payment of payments) {
|
||||||
|
try {
|
||||||
|
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
|
||||||
|
const twoTierPref = bodyshop.accountingconfig.twotierpref;
|
||||||
|
|
||||||
|
//Replace this with a for-each loop to check every single Job that's included in the list.
|
||||||
|
|
||||||
|
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
||||||
|
if (isThreeTier || twoTierPref === "source") {
|
||||||
|
//Insert the insurance company tier.
|
||||||
|
//Query for top level customer, the insurance company name.
|
||||||
|
insCoCustomerTier = await QueryInsuranceCo(
|
||||||
|
oauthClient,
|
||||||
|
req,
|
||||||
|
payment.job
|
||||||
|
);
|
||||||
|
if (!insCoCustomerTier) {
|
||||||
|
//Creating the Insurance Customer.
|
||||||
|
insCoCustomerTier = await InsertInsuranceCo(
|
||||||
|
oauthClient,
|
||||||
|
req,
|
||||||
|
payment.job,
|
||||||
|
bodyshop
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (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);
|
||||||
|
//Query for the owner itself.
|
||||||
|
if (!ownerCustomerTier) {
|
||||||
|
ownerCustomerTier = await InsertOwner(
|
||||||
|
oauthClient,
|
||||||
|
req,
|
||||||
|
payment.job,
|
||||||
|
isThreeTier,
|
||||||
|
insCoCustomerTier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Query for the Job or Create it.
|
||||||
|
jobTier = await QueryJob(oauthClient, req, payment.job);
|
||||||
|
|
||||||
|
// Need to validate that the job tier is associated to the right individual?
|
||||||
|
|
||||||
|
if (!jobTier) {
|
||||||
|
jobTier = await InsertJob(
|
||||||
|
oauthClient,
|
||||||
|
req,
|
||||||
|
payment.job,
|
||||||
|
isThreeTier,
|
||||||
|
ownerCustomerTier
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await InsertPayment(oauthClient, req, payment, jobTier);
|
||||||
|
ret.push({ paymentid: payment.id, success: true });
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||||
|
error:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
|
});
|
||||||
|
|
||||||
|
ret.push({
|
||||||
|
paymentid: payment.id,
|
||||||
|
success: false,
|
||||||
|
errorMessage:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json(ret);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
logger.log("qbo-payment-create-error", "ERROR", req.user.email, { error });
|
||||||
|
res.status(400).json(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function InsertPayment(oauthClient, req, payment, parentRef) {
|
||||||
|
const { paymentMethods, invoices } = await QueryMetaData(
|
||||||
|
oauthClient,
|
||||||
|
req,
|
||||||
|
payment.job.ro_number
|
||||||
|
);
|
||||||
|
|
||||||
|
if (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"),
|
||||||
|
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
|
||||||
|
DocNumber: payment.paymentnum,
|
||||||
|
TotalAmt: Dinero({
|
||||||
|
amount: Math.round(payment.amount * 100),
|
||||||
|
}).toFormat(DineroQbFormat),
|
||||||
|
PaymentMethodRef: {
|
||||||
|
value: paymentMethods[payment.type],
|
||||||
|
},
|
||||||
|
Line: [
|
||||||
|
{
|
||||||
|
Amount: Dinero({
|
||||||
|
amount: Math.round(payment.amount * 100),
|
||||||
|
}).toFormat(DineroQbFormat),
|
||||||
|
LinkedTxn: [
|
||||||
|
{
|
||||||
|
TxnId: invoices[0].Id,
|
||||||
|
TxnType: "Invoice",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
|
||||||
|
paymentQbo,
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const result = await oauthClient.makeApiCall({
|
||||||
|
url: urlBuilder(req.cookies.qbo_realmId, "payment"),
|
||||||
|
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: JSON.stringify(error),
|
||||||
|
method: "InsertPayment",
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function QueryMetaData(oauthClient, req, ro_number) {
|
||||||
|
const invoice = await oauthClient.makeApiCall({
|
||||||
|
url: urlBuilder(
|
||||||
|
req.cookies.qbo_realmId,
|
||||||
|
"query",
|
||||||
|
`select * From Invoice where DocNumber = '${ro_number}'`
|
||||||
|
),
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const paymentMethods = await oauthClient.makeApiCall({
|
||||||
|
url: urlBuilder(
|
||||||
|
req.cookies.qbo_realmId,
|
||||||
|
"query",
|
||||||
|
`select * From PaymentMethod`
|
||||||
|
),
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setNewRefreshToken(req.user.email, paymentMethods);
|
||||||
|
|
||||||
|
// const classes = await oauthClient.makeApiCall({
|
||||||
|
// url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
|
||||||
|
// method: "POST",
|
||||||
|
// headers: {
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
const paymentMethodMapping = {};
|
||||||
|
|
||||||
|
paymentMethods.json &&
|
||||||
|
paymentMethods.json.QueryResponse &&
|
||||||
|
paymentMethods.json.QueryResponse.PaymentMethod &&
|
||||||
|
paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => {
|
||||||
|
paymentMethodMapping[t.Name] = t.Id;
|
||||||
|
});
|
||||||
|
|
||||||
|
// const accountMapping = {};
|
||||||
|
|
||||||
|
// accounts.json &&
|
||||||
|
// accounts.json.QueryResponse &&
|
||||||
|
// accounts.json.QueryResponse.Account.forEach((t) => {
|
||||||
|
// accountMapping[t.Name] = t.Id;
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const classMapping = {};
|
||||||
|
// classes.json &&
|
||||||
|
// classes.json.QueryResponse &&
|
||||||
|
// classes.json.QueryResponse.Class.forEach((t) => {
|
||||||
|
// accountMapping[t.Name] = t.Id;
|
||||||
|
// });
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentMethods: paymentMethodMapping,
|
||||||
|
invoices:
|
||||||
|
invoice.json &&
|
||||||
|
invoice.json.QueryResponse &&
|
||||||
|
invoice.json.QueryResponse.Invoice,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ exports.default = async (req, res) => {
|
|||||||
//Replace this with a for-each loop to check every single Job that's included in the list.
|
//Replace this with a for-each loop to check every single Job that's included in the list.
|
||||||
|
|
||||||
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
||||||
if (isThreeTier || twoTierPref === "source") {
|
if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
|
||||||
//Insert the insurance company tier.
|
//Insert the insurance company tier.
|
||||||
//Query for top level customer, the insurance company name.
|
//Query for top level customer, the insurance company name.
|
||||||
insCoCustomerTier = await QueryInsuranceCo(oauthClient, req, job);
|
insCoCustomerTier = await QueryInsuranceCo(oauthClient, req, job);
|
||||||
@@ -80,8 +80,8 @@ exports.default = async (req, res) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(insCoCustomerTier);
|
|
||||||
if (isThreeTier || twoTierPref === "name") {
|
if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
|
||||||
//Insert the name/owner and account for whether the source should be the ins co in 3 tier..
|
//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, req, job);
|
||||||
//Query for the owner itself.
|
//Query for the owner itself.
|
||||||
@@ -95,7 +95,7 @@ exports.default = async (req, res) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(ownerCustomerTier);
|
|
||||||
//Query for the Job or Create it.
|
//Query for the Job or Create it.
|
||||||
jobTier = await QueryJob(oauthClient, req, job);
|
jobTier = await QueryJob(oauthClient, req, job);
|
||||||
|
|
||||||
@@ -106,18 +106,20 @@ exports.default = async (req, res) => {
|
|||||||
oauthClient,
|
oauthClient,
|
||||||
req,
|
req,
|
||||||
job,
|
job,
|
||||||
isThreeTier,
|
|
||||||
ownerCustomerTier
|
ownerCustomerTier || insCoCustomerTier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
console.log(jobTier);
|
|
||||||
await InsertInvoice(oauthClient, req, job, bodyshop, jobTier);
|
await InsertInvoice(oauthClient, req, job, bodyshop, jobTier);
|
||||||
ret.push({ jobid: job.id, success: true });
|
ret.push({ jobid: job.id, success: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ret.push({
|
ret.push({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: error.message,
|
errorMessage:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
JSON.stringify(error),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -160,6 +162,7 @@ async function QueryInsuranceCo(oauthClient, req, job) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
exports.QueryInsuranceCo = QueryInsuranceCo;
|
||||||
async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
|
async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
|
||||||
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
|
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
|
||||||
|
|
||||||
@@ -192,7 +195,7 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
exports.InsertInsuranceCo = InsertInsuranceCo;
|
||||||
async function QueryOwner(oauthClient, req, job) {
|
async function QueryOwner(oauthClient, req, job) {
|
||||||
const ownerName = generateOwnerTier(job, true, null);
|
const ownerName = generateOwnerTier(job, true, null);
|
||||||
const result = await oauthClient.makeApiCall({
|
const result = await oauthClient.makeApiCall({
|
||||||
@@ -214,7 +217,7 @@ async function QueryOwner(oauthClient, req, job) {
|
|||||||
result.json.QueryResponse.Customer[0]
|
result.json.QueryResponse.Customer[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
exports.QueryOwner = QueryOwner;
|
||||||
async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
|
async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||||
const ownerName = generateOwnerTier(job, true, null);
|
const ownerName = generateOwnerTier(job, true, null);
|
||||||
const Customer = {
|
const Customer = {
|
||||||
@@ -254,7 +257,7 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
exports.InsertOwner = InsertOwner;
|
||||||
async function QueryJob(oauthClient, req, job) {
|
async function QueryJob(oauthClient, req, job) {
|
||||||
const result = await oauthClient.makeApiCall({
|
const result = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(
|
url: urlBuilder(
|
||||||
@@ -275,8 +278,8 @@ async function QueryJob(oauthClient, req, job) {
|
|||||||
result.json.QueryResponse.Customer[0]
|
result.json.QueryResponse.Customer[0]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
exports.QueryJob = QueryJob;
|
||||||
async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
|
async function InsertJob(oauthClient, req, job, parentTierRef) {
|
||||||
const Customer = {
|
const Customer = {
|
||||||
DisplayName: job.ro_number,
|
DisplayName: job.ro_number,
|
||||||
BillAddr: {
|
BillAddr: {
|
||||||
@@ -286,14 +289,11 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
|
|||||||
PostalCode: job.ownr_zip,
|
PostalCode: job.ownr_zip,
|
||||||
CountrySubDivisionCode: job.ownr_st,
|
CountrySubDivisionCode: job.ownr_st,
|
||||||
},
|
},
|
||||||
...(isThreeTier
|
|
||||||
? {
|
Job: true,
|
||||||
Job: true,
|
ParentRef: {
|
||||||
ParentRef: {
|
value: parentTierRef.Id,
|
||||||
value: parentTierRef.Id,
|
},
|
||||||
},
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const result = await oauthClient.makeApiCall({
|
const result = await oauthClient.makeApiCall({
|
||||||
@@ -314,7 +314,7 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
exports.InsertJob = InsertJob;
|
||||||
async function QueryMetaData(oauthClient, req) {
|
async function QueryMetaData(oauthClient, req) {
|
||||||
const items = await oauthClient.makeApiCall({
|
const items = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Item`),
|
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Item`),
|
||||||
@@ -344,6 +344,7 @@ async function QueryMetaData(oauthClient, req) {
|
|||||||
|
|
||||||
taxCodes.json &&
|
taxCodes.json &&
|
||||||
taxCodes.json.QueryResponse &&
|
taxCodes.json.QueryResponse &&
|
||||||
|
taxCodes.json.QueryResponse.TaxCode &&
|
||||||
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
||||||
taxCodeMapping[t.Name] = t.Id;
|
taxCodeMapping[t.Name] = t.Id;
|
||||||
});
|
});
|
||||||
@@ -352,6 +353,7 @@ async function QueryMetaData(oauthClient, req) {
|
|||||||
|
|
||||||
items.json &&
|
items.json &&
|
||||||
items.json.QueryResponse &&
|
items.json.QueryResponse &&
|
||||||
|
items.json.QueryResponse.Item &&
|
||||||
items.json.QueryResponse.Item.forEach((t) => {
|
items.json.QueryResponse.Item.forEach((t) => {
|
||||||
itemMapping[t.Name] = t.Id;
|
itemMapping[t.Name] = t.Id;
|
||||||
});
|
});
|
||||||
@@ -359,6 +361,7 @@ async function QueryMetaData(oauthClient, req) {
|
|||||||
const classMapping = {};
|
const classMapping = {};
|
||||||
classes.json &&
|
classes.json &&
|
||||||
classes.json.QueryResponse &&
|
classes.json.QueryResponse &&
|
||||||
|
classes.json.QueryResponse.Class &&
|
||||||
classes.json.QueryResponse.Class.forEach((t) => {
|
classes.json.QueryResponse.Class.forEach((t) => {
|
||||||
itemMapping[t.Name] = t.Id;
|
itemMapping[t.Name] = t.Id;
|
||||||
});
|
});
|
||||||
@@ -391,15 +394,20 @@ async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) {
|
|||||||
...(bodyshop.accountingconfig.printlater
|
...(bodyshop.accountingconfig.printlater
|
||||||
? { PrintStatus: "NeedToPrint" }
|
? { PrintStatus: "NeedToPrint" }
|
||||||
: {}),
|
: {}),
|
||||||
...(bodyshop.accountingconfig.emaillater
|
...(bodyshop.accountingconfig.emaillater && job.ownr_ea
|
||||||
? { EmailStatus: "NeedToSend" }
|
? { EmailStatus: "NeedToSend" }
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
||||||
|
invoiceObj,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await oauthClient.makeApiCall({
|
const result = await oauthClient.makeApiCall({
|
||||||
url: urlBuilder(req.cookies.qbo_realmId, "invoice"),
|
url: urlBuilder(req.cookies.qbo_realmId, "invoice"),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ require("dotenv").config({
|
|||||||
|
|
||||||
function urlBuilder(realmId, object, query = null) {
|
function urlBuilder(realmId, object, query = null) {
|
||||||
return `https://${
|
return `https://${
|
||||||
process.env.NODE_ENV === "development" || !process.env.NODE_ENV
|
process.env.NODE_ENV === "production" ? "" : "sandbox-"
|
||||||
? "sandbox-"
|
|
||||||
: ""
|
|
||||||
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${
|
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${
|
||||||
query ? `?query=${encodeURIComponent(query)}` : ""
|
query ? `?query=${encodeURIComponent(query)}` : ""
|
||||||
}`;
|
}`;
|
||||||
@@ -22,3 +20,4 @@ exports.authorize = require("./qbo-authorize").default;
|
|||||||
exports.refresh = require("./qbo-callback").refresh;
|
exports.refresh = require("./qbo-callback").refresh;
|
||||||
exports.receivables = require("./qbo-receivables").default;
|
exports.receivables = require("./qbo-receivables").default;
|
||||||
exports.payables = require("./qbo-payables").default;
|
exports.payables = require("./qbo-payables").default;
|
||||||
|
exports.payments = require("./qbo-payments").default;
|
||||||
|
|||||||
@@ -142,9 +142,6 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
|
|||||||
payment.stripeid || ""
|
payment.stripeid || ""
|
||||||
} ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`,
|
} ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`,
|
||||||
IsAutoApply: true,
|
IsAutoApply: true,
|
||||||
// AppliedToTxnAdd:{
|
|
||||||
// T
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -284,39 +284,54 @@ function CalculatePartsTotals(jobLines) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
if (!value.part_type) return acc;
|
if (
|
||||||
|
!value.part_type &&
|
||||||
|
value.db_ref !== "900510" &&
|
||||||
|
value.db_ref !== "900511"
|
||||||
|
)
|
||||||
|
return acc;
|
||||||
return {
|
return {
|
||||||
...acc,
|
...acc,
|
||||||
parts: {
|
parts: {
|
||||||
...acc.parts,
|
...acc.parts,
|
||||||
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
prt_dsmk_total: acc.parts.prt_dsmk_total.add(
|
||||||
value.prt_dsmk_m && value.prt_dsmk_m !== 0
|
value.prt_dsmk_m && value.prt_dsmk_m !== 0
|
||||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||||
: Dinero({
|
: Dinero({
|
||||||
amount: Math.round(value.act_price * 100),
|
amount: Math.round(value.act_price * 100),
|
||||||
})
|
})
|
||||||
.multiply(value.part_qty || 0)
|
.multiply(value.part_qty || 0)
|
||||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||||
),
|
),
|
||||||
list: {
|
...(value.part_type
|
||||||
...acc.parts.list,
|
? {
|
||||||
[value.part_type]:
|
list: {
|
||||||
acc.parts.list[value.part_type] &&
|
...acc.parts.list,
|
||||||
acc.parts.list[value.part_type].total
|
[value.part_type]:
|
||||||
? {
|
acc.parts.list[value.part_type] &&
|
||||||
total: acc.parts.list[value.part_type].total.add(
|
acc.parts.list[value.part_type].total
|
||||||
Dinero({
|
? {
|
||||||
amount: Math.round((value.act_price || 0) * 100),
|
total: acc.parts.list[
|
||||||
}).multiply(value.part_qty || 0)
|
value.part_type
|
||||||
),
|
].total.add(
|
||||||
}
|
Dinero({
|
||||||
: {
|
amount: Math.round(
|
||||||
total: Dinero({
|
(value.act_price || 0) * 100
|
||||||
amount: Math.round((value.act_price || 0) * 100),
|
),
|
||||||
}).multiply(value.part_qty || 0),
|
}).multiply(value.part_qty || 0)
|
||||||
},
|
),
|
||||||
},
|
}
|
||||||
|
: {
|
||||||
|
total: Dinero({
|
||||||
|
amount: Math.round(
|
||||||
|
(value.act_price || 0) * 100
|
||||||
|
),
|
||||||
|
}).multiply(value.part_qty || 0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
subtotal: acc.parts.subtotal
|
subtotal: acc.parts.subtotal
|
||||||
.add(
|
.add(
|
||||||
Dinero({
|
Dinero({
|
||||||
@@ -325,13 +340,13 @@ function CalculatePartsTotals(jobLines) {
|
|||||||
)
|
)
|
||||||
.add(
|
.add(
|
||||||
value.prt_dsmk_m && value.prt_dsmk_m !== 0
|
value.prt_dsmk_m && value.prt_dsmk_m !== 0
|
||||||
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
? Dinero({ amount: Math.round(value.prt_dsmk_m * 100) })
|
||||||
: Dinero({
|
: Dinero({
|
||||||
amount: Math.round(value.act_price * 100),
|
amount: Math.round(value.act_price * 100),
|
||||||
})
|
})
|
||||||
.multiply(value.part_qty || 0)
|
.multiply(value.part_qty || 0)
|
||||||
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
.percentage(Math.abs(value.prt_dsmk_p || 0))
|
||||||
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
.multiply(value.prt_dsmk_p > 0 ? 1 : -1)
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -465,13 +480,13 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
.multiply(val.part_qty || 0)
|
.multiply(val.part_qty || 0)
|
||||||
.add(
|
.add(
|
||||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||||
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
|
||||||
: Dinero({
|
: Dinero({
|
||||||
amount: Math.round(val.act_price * 100),
|
amount: Math.round(val.act_price * 100),
|
||||||
})
|
})
|
||||||
.multiply(val.part_qty || 0)
|
.multiply(val.part_qty || 0)
|
||||||
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
.percentage(Math.abs(val.prt_dsmk_p || 0))
|
||||||
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
|
||||||
)
|
)
|
||||||
.percentage(
|
.percentage(
|
||||||
((job.parts_tax_rates &&
|
((job.parts_tax_rates &&
|
||||||
@@ -481,6 +496,10 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
val.part_type.startsWith("PAG") &&
|
val.part_type.startsWith("PAG") &&
|
||||||
BackupGlassTax &&
|
BackupGlassTax &&
|
||||||
BackupGlassTax.prt_tax_rt) ||
|
BackupGlassTax.prt_tax_rt) ||
|
||||||
|
(!val.part_type &&
|
||||||
|
val.db_ref === "900510" &&
|
||||||
|
job.parts_tax_rates["PAN"] &&
|
||||||
|
job.parts_tax_rates["PAN"].prt_tax_rt) ||
|
||||||
0) * 100
|
0) * 100
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user