Added payment export BOD-147
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
<babeledit_project be_version="2.6.1" version="1.2">
|
<babeledit_project version="1.2" be_version="2.6.1">
|
||||||
<!--
|
<!--
|
||||||
|
|
||||||
BabelEdit project file
|
BabelEdit project file
|
||||||
@@ -12810,6 +12810,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>accounting-payments</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>accounting-receivables</name>
|
<name>accounting-receivables</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -12873,6 +12894,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>allpayments</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>availablejobs</name>
|
<name>availablejobs</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -13062,6 +13104,27 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
|
<concept_node>
|
||||||
|
<name>export</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>home</name>
|
<name>home</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
@@ -13146,27 +13209,6 @@
|
|||||||
</translation>
|
</translation>
|
||||||
</translations>
|
</translations>
|
||||||
</concept_node>
|
</concept_node>
|
||||||
<concept_node>
|
|
||||||
<name>payments</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>productionboard</name>
|
<name>productionboard</name>
|
||||||
<definition_loaded>false</definition_loaded>
|
<definition_loaded>false</definition_loaded>
|
||||||
|
|||||||
@@ -0,0 +1,189 @@
|
|||||||
|
import { Input, Table } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import { alphaSort } from "../../utils/sorters";
|
||||||
|
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||||
|
import { PaymentsExportAllButton } from "../payments-export-all-button/payments-export-all-button.component";
|
||||||
|
|
||||||
|
export default function AccountingPayablesTableComponent({
|
||||||
|
loading,
|
||||||
|
payments,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [selectedPayments, setSelectedPayments] = useState([]);
|
||||||
|
const [transInProgress, setTransInProgress] = useState(false);
|
||||||
|
const [state, setState] = useState({
|
||||||
|
sortedInfo: {},
|
||||||
|
search: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.ro_number"),
|
||||||
|
dataIndex: "ro_number",
|
||||||
|
key: "ro_number",
|
||||||
|
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={"/manage/jobs/" + record.job.id}>{record.job.ro_number}</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.est_number"),
|
||||||
|
dataIndex: "est_number",
|
||||||
|
key: "est_number",
|
||||||
|
sorter: (a, b) => a.job.est_number - b.job.est_number,
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "est_number" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => (
|
||||||
|
<Link to={"/manage/jobs/" + record.job.id}>
|
||||||
|
{record.job.est_number}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("jobs.fields.owner"),
|
||||||
|
dataIndex: "owner",
|
||||||
|
key: "owner",
|
||||||
|
ellipsis: true,
|
||||||
|
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln),
|
||||||
|
sortOrder:
|
||||||
|
state.sortedInfo.columnKey === "ownr_ln" && state.sortedInfo.order,
|
||||||
|
render: (text, record) => {
|
||||||
|
return record.job.owner ? (
|
||||||
|
<Link to={"/manage/owners/" + record.job.owner.id}>
|
||||||
|
{`${record.job.ownr_fn || ""} ${record.job.ownr_ln || ""}`}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span>{`${record.job.ownr_fn || ""} ${
|
||||||
|
record.job.ownr_ln || ""
|
||||||
|
}`}</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("payments.fields.amount"),
|
||||||
|
dataIndex: "amount",
|
||||||
|
key: "amount",
|
||||||
|
render: (text, record) => (
|
||||||
|
<CurrencyFormatter>{record.amount}</CurrencyFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("payments.fields.memo"),
|
||||||
|
dataIndex: "memo",
|
||||||
|
key: "memo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("payments.fields.transactionid"),
|
||||||
|
dataIndex: "transactionid",
|
||||||
|
key: "transactionid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("payments.fields.stripeid"),
|
||||||
|
dataIndex: "stripeid",
|
||||||
|
key: "stripeid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("payments.fields.created_at"),
|
||||||
|
dataIndex: "created_at",
|
||||||
|
key: "created_at",
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("payments.fields.exportedat"),
|
||||||
|
dataIndex: "exportedat",
|
||||||
|
key: "exportedat",
|
||||||
|
render: (text, record) => (
|
||||||
|
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
title: t("general.labels.actions"),
|
||||||
|
dataIndex: "actions",
|
||||||
|
key: "actions",
|
||||||
|
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||||
|
|
||||||
|
render: (text, record) => (
|
||||||
|
<div>
|
||||||
|
<PaymentExportButton
|
||||||
|
paymentId={record.id}
|
||||||
|
disabled={transInProgress || !!record.exportedat}
|
||||||
|
loadingCallback={setTransInProgress}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleSearch = (e) => {
|
||||||
|
setState({ ...state, search: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataSource = state.search
|
||||||
|
? payments.filter(
|
||||||
|
(v) =>
|
||||||
|
(v.vendor.name || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(state.search.toLowerCase()) ||
|
||||||
|
(v.invoice_number || "")
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(state.search.toLowerCase())
|
||||||
|
)
|
||||||
|
: payments;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Table
|
||||||
|
loading={loading}
|
||||||
|
title={() => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
value={state.search}
|
||||||
|
onChange={handleSearch}
|
||||||
|
placeholder={t("general.labels.search")}
|
||||||
|
allowClear
|
||||||
|
/>
|
||||||
|
<PaymentsExportAllButton
|
||||||
|
paymentIds={selectedPayments}
|
||||||
|
disabled={transInProgress}
|
||||||
|
loadingCallback={setTransInProgress}
|
||||||
|
completedCallback={setSelectedPayments}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
dataSource={dataSource}
|
||||||
|
size="small"
|
||||||
|
pagination={{ position: "top", pageSize: 50 }}
|
||||||
|
columns={columns}
|
||||||
|
rowKey="id"
|
||||||
|
onChange={handleTableChange}
|
||||||
|
rowSelection={{
|
||||||
|
onSelectAll: (selected, selectedRows) =>
|
||||||
|
setSelectedPayments(selectedRows.map((i) => i.id)),
|
||||||
|
onSelect: (record, selected, selectedRows, nativeEvent) => {
|
||||||
|
setSelectedPayments(selectedRows.map((i) => i.id));
|
||||||
|
},
|
||||||
|
getCheckboxProps: (record) => ({
|
||||||
|
disabled: record.exported,
|
||||||
|
}),
|
||||||
|
selectedRowKeys: selectedPayments,
|
||||||
|
type: "checkbox",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -232,19 +232,30 @@ function Header({
|
|||||||
>
|
>
|
||||||
{t("menus.header.entertimeticket")}
|
{t("menus.header.entertimeticket")}
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="receivables">
|
|
||||||
<Link to="/manage/accounting/receivables">
|
<Menu.SubMenu title={t("menus.header.export")}>
|
||||||
{t("menus.header.accounting-receivables")}
|
<Menu.Item key="receivables">
|
||||||
|
<Link to="/manage/accounting/receivables">
|
||||||
|
{t("menus.header.accounting-receivables")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="payables">
|
||||||
|
<Link to="/manage/accounting/payables">
|
||||||
|
{t("menus.header.accounting-payables")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="payments">
|
||||||
|
<Link to="/manage/accounting/payments">
|
||||||
|
{t("menus.header.accounting-payments")}
|
||||||
|
</Link>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu.SubMenu>
|
||||||
|
|
||||||
|
<Menu.Item key="allpayments">
|
||||||
|
<Link to="/manage/payments">
|
||||||
|
{t("menus.header.allpayments")}
|
||||||
</Link>
|
</Link>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="payables">
|
|
||||||
<Link to="/manage/accounting/payables">
|
|
||||||
{t("menus.header.accounting-payables")}
|
|
||||||
</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
<Menu.Item key="payments">
|
|
||||||
<Link to="/manage/payments">{t("menus.header.payments")}</Link>
|
|
||||||
</Menu.Item>
|
|
||||||
</Menu.SubMenu>
|
</Menu.SubMenu>
|
||||||
<Menu.SubMenu title={t("menus.header.shop")}>
|
<Menu.SubMenu title={t("menus.header.shop")}>
|
||||||
<Menu.Item key="shop">
|
<Menu.Item key="shop">
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ export function JobsCloseExportButton({ bodyshop, jobId, disabled }) {
|
|||||||
let PartnerResponse;
|
let PartnerResponse;
|
||||||
try {
|
try {
|
||||||
PartnerResponse = await axios.post(
|
PartnerResponse = await axios.post(
|
||||||
// "http://localhost:1337/qb/",
|
"http://localhost:1337/qb/",
|
||||||
"http://b47e67f9cbe3.ngrok.io/qb/",
|
// "http://609feaeae986.ngrok.io/qb/",
|
||||||
QbXmlResponse.data,
|
QbXmlResponse.data,
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import { useMutation } from "@apollo/react-hooks";
|
||||||
|
import { Button, notification } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { auth } from "../../firebase/firebase.utils";
|
||||||
|
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PaymentExportButton({
|
||||||
|
bodyshop,
|
||||||
|
paymentId,
|
||||||
|
disabled,
|
||||||
|
loadingCallback,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleQbxml = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
if (!!loadingCallback) loadingCallback(true);
|
||||||
|
|
||||||
|
let QbXmlResponse;
|
||||||
|
try {
|
||||||
|
QbXmlResponse = await axios.post(
|
||||||
|
"/accounting/qbxml/payments",
|
||||||
|
{ payments: [paymentId] },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
|
||||||
|
try {
|
||||||
|
PartnerResponse = await axios.post(
|
||||||
|
"http://localhost:1337/qb/",
|
||||||
|
//"http://609feaeae986.ngrok.io/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);
|
||||||
|
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
||||||
|
const successfulTransactions = PartnerResponse.data.filter(
|
||||||
|
(r) => r.success
|
||||||
|
);
|
||||||
|
if (failedTransactions.length > 0) {
|
||||||
|
//Uh oh. At least one was no good.
|
||||||
|
failedTransactions.map((ft) =>
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting", {
|
||||||
|
error: ft.errorMessage || "",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (successfulTransactions.length > 0) {
|
||||||
|
const paymentUpdateResponse = await updatePayment({
|
||||||
|
variables: {
|
||||||
|
paymentIdList: successfulTransactions.map((st) => st.id),
|
||||||
|
payment: {
|
||||||
|
exportedat: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!!!paymentUpdateResponse.errors) {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("jobs.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.exporting", {
|
||||||
|
error: JSON.stringify(paymentUpdateResponse.error),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={handleQbxml}
|
||||||
|
loading={loading}
|
||||||
|
disabled={disabled}
|
||||||
|
type="dashed"
|
||||||
|
>
|
||||||
|
{t("jobs.actions.export")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(PaymentExportButton);
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { useMutation } from "@apollo/react-hooks";
|
||||||
|
import { Button, notification } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { auth } from "../../firebase/firebase.utils";
|
||||||
|
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
export function PaymentsExportAllButton({
|
||||||
|
bodyshop,
|
||||||
|
paymentIds,
|
||||||
|
disabled,
|
||||||
|
loadingCallback,
|
||||||
|
completedCallback,
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [updatePayments] = useMutation(UPDATE_PAYMENTS);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleQbxml = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
if (!!loadingCallback) loadingCallback(true);
|
||||||
|
|
||||||
|
let QbXmlResponse;
|
||||||
|
try {
|
||||||
|
QbXmlResponse = await axios.post(
|
||||||
|
"/accounting/qbxml/payments",
|
||||||
|
{ payments: paymentIds },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
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;
|
||||||
|
|
||||||
|
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);
|
||||||
|
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
||||||
|
const successfulTransactions = PartnerResponse.data.filter(
|
||||||
|
(r) => r.success
|
||||||
|
);
|
||||||
|
if (failedTransactions.length > 0) {
|
||||||
|
//Uh oh. At least one was no good.
|
||||||
|
failedTransactions.map((ft) =>
|
||||||
|
notification["error"]({
|
||||||
|
message: t("payments.errors.exporting", {
|
||||||
|
error: ft.errorMessage || "",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (successfulTransactions.length > 0) {
|
||||||
|
const paymentUpdateResponse = await updatePayments({
|
||||||
|
variables: {
|
||||||
|
paymentIdList: successfulTransactions.map((st) => st.id),
|
||||||
|
payment: {
|
||||||
|
//exported: true,
|
||||||
|
exportedat: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!!!paymentUpdateResponse.errors) {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("jobs.successes.exported"),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.exporting", {
|
||||||
|
error: JSON.stringify(paymentUpdateResponse.error),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!completedCallback) completedCallback([]);
|
||||||
|
if (!!loadingCallback) loadingCallback(false);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={handleQbxml}
|
||||||
|
loading={loading}
|
||||||
|
disabled={disabled}
|
||||||
|
type="dashed"
|
||||||
|
>
|
||||||
|
{t("jobs.actions.exportselected")}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, null)(PaymentsExportAllButton);
|
||||||
@@ -10,11 +10,8 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
import { QUERY_ALL_ACTIVE_JOBS } from "../../graphql/jobs.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
|
||||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
|
||||||
import { alphaSort } from "../../utils/sorters";
|
import { alphaSort } from "../../utils/sorters";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
|||||||
@@ -48,3 +48,29 @@ export const QUERY_INVOICES_FOR_EXPORT = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const QUERY_PAYMENTS_FOR_EXPORT = gql`
|
||||||
|
query QUERY_PAYMENTS_FOR_EXPORT {
|
||||||
|
payments(
|
||||||
|
where: { exportedat: { _eq: null } }
|
||||||
|
order_by: { created_at: desc }
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
amount
|
||||||
|
job {
|
||||||
|
ro_number
|
||||||
|
est_number
|
||||||
|
id
|
||||||
|
ownr_fn
|
||||||
|
ownr_ln
|
||||||
|
ownr_co_nm
|
||||||
|
}
|
||||||
|
payer
|
||||||
|
memo
|
||||||
|
exportedat
|
||||||
|
stripeid
|
||||||
|
created_at
|
||||||
|
transactionid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ export const UPDATE_INVOICE = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const UPDATE_INVOICES = gql`
|
export const UPDATE_INVOICES = gql`
|
||||||
mutation UPDATE_INVOICES(
|
mutation UPDATE_INVOICES(
|
||||||
$invoiceIdList: [uuid!]!
|
$invoiceIdList: [uuid!]!
|
||||||
|
|||||||
@@ -48,3 +48,28 @@ export const QUERY_ALL_PAYMENTS_PAGINATED = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_PAYMENT = gql`
|
||||||
|
mutation UPDATE_PAYMENT($paymentId: uuid!, $payment: payments_set_input!) {
|
||||||
|
update_payments(where: { id: { _eq: $paymentId } }, _set: $payment) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
exported_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_PAYMENTS = gql`
|
||||||
|
mutation UPDATE_PAYMENTS(
|
||||||
|
$paymentIdList: [uuid!]!
|
||||||
|
$payment: payments_set_input!
|
||||||
|
) {
|
||||||
|
update_payments(where: { id: { _in: $paymentIdList } }, _set: $payment) {
|
||||||
|
returning {
|
||||||
|
id
|
||||||
|
exportedat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { useQuery } from "@apollo/react-hooks";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import AccountingPaymentsTable from "../../components/accounting-payments-table/accounting-payments-table.component";
|
||||||
|
import AlertComponent from "../../components/alert/alert.component";
|
||||||
|
import { QUERY_PAYMENTS_FOR_EXPORT } from "../../graphql/accounting.queries";
|
||||||
|
import { setBreadcrumbs } from "../../redux/application/application.actions";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
|
});
|
||||||
|
export function AccountingPaymentsContainer({ bodyshop, setBreadcrumbs }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = t("titles.accounting-payments");
|
||||||
|
setBreadcrumbs([
|
||||||
|
{
|
||||||
|
link: "/manage/accounting/payments",
|
||||||
|
label: t("titles.bc.accounting-payments"),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}, [t, setBreadcrumbs]);
|
||||||
|
|
||||||
|
const { loading, error, data } = useQuery(QUERY_PAYMENTS_FOR_EXPORT);
|
||||||
|
|
||||||
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AccountingPaymentsTable
|
||||||
|
loadaing={loading}
|
||||||
|
payments={data ? data.payments : []}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(AccountingPaymentsContainer);
|
||||||
@@ -104,6 +104,9 @@ const AccountingReceivables = lazy(() =>
|
|||||||
const AccountingPayables = lazy(() =>
|
const AccountingPayables = lazy(() =>
|
||||||
import("../accounting-payables/accounting-payables.container")
|
import("../accounting-payables/accounting-payables.container")
|
||||||
);
|
);
|
||||||
|
const AccountingPayments = lazy(() =>
|
||||||
|
import("../accounting-payments/accounting-payments.container")
|
||||||
|
);
|
||||||
const AllJobs = lazy(() => import("../jobs-all/jobs-all.container"));
|
const AllJobs = lazy(() => import("../jobs-all/jobs-all.container"));
|
||||||
const JobsClose = lazy(() => import("../jobs-close/jobs-close.container"));
|
const JobsClose = lazy(() => import("../jobs-close/jobs-close.container"));
|
||||||
const ShopCsiPageContainer = lazy(() =>
|
const ShopCsiPageContainer = lazy(() =>
|
||||||
@@ -163,10 +166,8 @@ export function Manage({ match, conflict }) {
|
|||||||
<Elements stripe={stripePromise}>
|
<Elements stripe={stripePromise}>
|
||||||
<PaymentModalContainer />
|
<PaymentModalContainer />
|
||||||
</Elements>
|
</Elements>
|
||||||
|
|
||||||
<Route exact path={`${match.path}`} component={ManageRootPage} />
|
<Route exact path={`${match.path}`} component={ManageRootPage} />
|
||||||
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
<Route exact path={`${match.path}/jobs`} component={JobsPage} />
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
@@ -304,6 +305,11 @@ export function Manage({ match, conflict }) {
|
|||||||
path={`${match.path}/accounting/payables`}
|
path={`${match.path}/accounting/payables`}
|
||||||
component={AccountingPayables}
|
component={AccountingPayables}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={`${match.path}/accounting/payments`}
|
||||||
|
component={AccountingPayments}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={`${match.path}/payments`}
|
path={`${match.path}/payments`}
|
||||||
|
|||||||
@@ -1,21 +1,8 @@
|
|||||||
import { useQuery } from "@apollo/react-hooks";
|
|
||||||
import queryString from "query-string";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useHistory, useLocation } from "react-router-dom";
|
|
||||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
|
||||||
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
|
||||||
import { Row, Col } from "antd";
|
|
||||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||||
|
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||||
|
|
||||||
export default function TechLookupContainer() {
|
export default function TechLookupContainer() {
|
||||||
const search = queryString.parse(useLocation().search);
|
|
||||||
const history = useHistory();
|
|
||||||
|
|
||||||
const { loading, error, data, refetch } = useQuery(SEARCH_FOR_JOBS, {
|
|
||||||
variables: { search: `%${search.ro_number}%` },
|
|
||||||
skip: !!!search.ro_number,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<TechLookupJobsList />
|
<TechLookupJobsList />
|
||||||
|
|||||||
@@ -780,9 +780,11 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"accounting": "Accounting",
|
"accounting": "Accounting",
|
||||||
"accounting-payables": "Payables",
|
"accounting-payables": "Payables",
|
||||||
|
"accounting-payments": "Payments",
|
||||||
"accounting-receivables": "Receivables",
|
"accounting-receivables": "Receivables",
|
||||||
"activejobs": "Active Jobs",
|
"activejobs": "Active Jobs",
|
||||||
"alljobs": "All Jobs",
|
"alljobs": "All Jobs",
|
||||||
|
"allpayments": "All Payments",
|
||||||
"availablejobs": "Available Jobs",
|
"availablejobs": "Available Jobs",
|
||||||
"courtesycars": "Courtesy Cars",
|
"courtesycars": "Courtesy Cars",
|
||||||
"courtesycars-all": "All Courtesy Cars",
|
"courtesycars-all": "All Courtesy Cars",
|
||||||
@@ -792,11 +794,11 @@
|
|||||||
"enterinvoices": "Enter Invoices",
|
"enterinvoices": "Enter Invoices",
|
||||||
"enterpayment": "Enter Payments",
|
"enterpayment": "Enter Payments",
|
||||||
"entertimeticket": "Enter Time Tickets",
|
"entertimeticket": "Enter Time Tickets",
|
||||||
|
"export": "Export",
|
||||||
"home": "Home",
|
"home": "Home",
|
||||||
"invoices": "Invoices",
|
"invoices": "Invoices",
|
||||||
"jobs": "Jobs",
|
"jobs": "Jobs",
|
||||||
"owners": "Owners",
|
"owners": "Owners",
|
||||||
"payments": "All Payments",
|
|
||||||
"productionboard": "Production Board",
|
"productionboard": "Production Board",
|
||||||
"productionlist": "Production - List",
|
"productionlist": "Production - List",
|
||||||
"schedule": "Schedule",
|
"schedule": "Schedule",
|
||||||
|
|||||||
@@ -780,9 +780,11 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"accounting": "",
|
"accounting": "",
|
||||||
"accounting-payables": "",
|
"accounting-payables": "",
|
||||||
|
"accounting-payments": "",
|
||||||
"accounting-receivables": "",
|
"accounting-receivables": "",
|
||||||
"activejobs": "Empleos activos",
|
"activejobs": "Empleos activos",
|
||||||
"alljobs": "",
|
"alljobs": "",
|
||||||
|
"allpayments": "",
|
||||||
"availablejobs": "Trabajos disponibles",
|
"availablejobs": "Trabajos disponibles",
|
||||||
"courtesycars": "",
|
"courtesycars": "",
|
||||||
"courtesycars-all": "",
|
"courtesycars-all": "",
|
||||||
@@ -792,11 +794,11 @@
|
|||||||
"enterinvoices": "",
|
"enterinvoices": "",
|
||||||
"enterpayment": "",
|
"enterpayment": "",
|
||||||
"entertimeticket": "",
|
"entertimeticket": "",
|
||||||
|
"export": "",
|
||||||
"home": "Casa",
|
"home": "Casa",
|
||||||
"invoices": "",
|
"invoices": "",
|
||||||
"jobs": "Trabajos",
|
"jobs": "Trabajos",
|
||||||
"owners": "propietarios",
|
"owners": "propietarios",
|
||||||
"payments": "",
|
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
"productionlist": "",
|
"productionlist": "",
|
||||||
"schedule": "Programar",
|
"schedule": "Programar",
|
||||||
|
|||||||
@@ -780,9 +780,11 @@
|
|||||||
"header": {
|
"header": {
|
||||||
"accounting": "",
|
"accounting": "",
|
||||||
"accounting-payables": "",
|
"accounting-payables": "",
|
||||||
|
"accounting-payments": "",
|
||||||
"accounting-receivables": "",
|
"accounting-receivables": "",
|
||||||
"activejobs": "Emplois actifs",
|
"activejobs": "Emplois actifs",
|
||||||
"alljobs": "",
|
"alljobs": "",
|
||||||
|
"allpayments": "",
|
||||||
"availablejobs": "Emplois disponibles",
|
"availablejobs": "Emplois disponibles",
|
||||||
"courtesycars": "",
|
"courtesycars": "",
|
||||||
"courtesycars-all": "",
|
"courtesycars-all": "",
|
||||||
@@ -792,11 +794,11 @@
|
|||||||
"enterinvoices": "",
|
"enterinvoices": "",
|
||||||
"enterpayment": "",
|
"enterpayment": "",
|
||||||
"entertimeticket": "",
|
"entertimeticket": "",
|
||||||
|
"export": "",
|
||||||
"home": "Accueil",
|
"home": "Accueil",
|
||||||
"invoices": "",
|
"invoices": "",
|
||||||
"jobs": "Emplois",
|
"jobs": "Emplois",
|
||||||
"owners": "Propriétaires",
|
"owners": "Propriétaires",
|
||||||
"payments": "",
|
|
||||||
"productionboard": "",
|
"productionboard": "",
|
||||||
"productionlist": "",
|
"productionlist": "",
|
||||||
"schedule": "Programme",
|
"schedule": "Programme",
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
- args:
|
||||||
|
cascade: false
|
||||||
|
read_only: false
|
||||||
|
sql: ALTER TABLE "public"."payments" DROP COLUMN "type";
|
||||||
|
type: run_sql
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
- args:
|
||||||
|
cascade: false
|
||||||
|
read_only: false
|
||||||
|
sql: ALTER TABLE "public"."payments" ADD COLUMN "type" text NULL;
|
||||||
|
type: run_sql
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: drop_insert_permission
|
||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
check:
|
||||||
|
job:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
columns:
|
||||||
|
- amount
|
||||||
|
- created_at
|
||||||
|
- exportedat
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- memo
|
||||||
|
- payer
|
||||||
|
- stripeid
|
||||||
|
- transactionid
|
||||||
|
- updated_at
|
||||||
|
set: {}
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: create_insert_permission
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: drop_insert_permission
|
||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
check:
|
||||||
|
job:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
columns:
|
||||||
|
- amount
|
||||||
|
- created_at
|
||||||
|
- exportedat
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- memo
|
||||||
|
- payer
|
||||||
|
- stripeid
|
||||||
|
- transactionid
|
||||||
|
- type
|
||||||
|
- updated_at
|
||||||
|
set: {}
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: create_insert_permission
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: drop_update_permission
|
||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- amount
|
||||||
|
- created_at
|
||||||
|
- exportedat
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- memo
|
||||||
|
- payer
|
||||||
|
- stripeid
|
||||||
|
- transactionid
|
||||||
|
- updated_at
|
||||||
|
filter:
|
||||||
|
job:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
set: {}
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: create_update_permission
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
- args:
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: drop_update_permission
|
||||||
|
- args:
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- amount
|
||||||
|
- created_at
|
||||||
|
- exportedat
|
||||||
|
- id
|
||||||
|
- jobid
|
||||||
|
- memo
|
||||||
|
- payer
|
||||||
|
- stripeid
|
||||||
|
- transactionid
|
||||||
|
- type
|
||||||
|
- updated_at
|
||||||
|
filter:
|
||||||
|
job:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
|
set: {}
|
||||||
|
role: user
|
||||||
|
table:
|
||||||
|
name: payments
|
||||||
|
schema: public
|
||||||
|
type: create_update_permission
|
||||||
@@ -3243,6 +3243,7 @@ tables:
|
|||||||
- payer
|
- payer
|
||||||
- stripeid
|
- stripeid
|
||||||
- transactionid
|
- transactionid
|
||||||
|
- type
|
||||||
- updated_at
|
- updated_at
|
||||||
select_permissions:
|
select_permissions:
|
||||||
- role: user
|
- role: user
|
||||||
@@ -3282,6 +3283,7 @@ tables:
|
|||||||
- payer
|
- payer
|
||||||
- stripeid
|
- stripeid
|
||||||
- transactionid
|
- transactionid
|
||||||
|
- type
|
||||||
- updated_at
|
- updated_at
|
||||||
filter:
|
filter:
|
||||||
job:
|
job:
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ app.post("/accounting/iif/receivables", accountingIIF.receivables);
|
|||||||
const accountQbxml = require("./server/accounting/qbxml/qbxml");
|
const accountQbxml = require("./server/accounting/qbxml/qbxml");
|
||||||
app.post("/accounting/qbxml/receivables", accountQbxml.receivables);
|
app.post("/accounting/qbxml/receivables", accountQbxml.receivables);
|
||||||
app.post("/accounting/qbxml/payables", accountQbxml.payables);
|
app.post("/accounting/qbxml/payables", accountQbxml.payables);
|
||||||
|
app.post("/accounting/qbxml/payments", accountQbxml.payments);
|
||||||
|
|
||||||
//Cloudinary Media Paths
|
//Cloudinary Media Paths
|
||||||
var media = require("./server/media/media");
|
var media = require("./server/media/media");
|
||||||
|
|||||||
106
server/accounting/qbxml/qbxml-payments.js
Normal file
106
server/accounting/qbxml/qbxml-payments.js
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||||
|
const path = require("path");
|
||||||
|
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
||||||
|
const queries = require("../../graphql-client/queries");
|
||||||
|
const Dinero = require("dinero.js");
|
||||||
|
var builder = require("xmlbuilder");
|
||||||
|
const moment = require("moment");
|
||||||
|
const QbXmlUtils = require("./qbxml-utils");
|
||||||
|
require("dotenv").config({
|
||||||
|
path: path.resolve(
|
||||||
|
process.cwd(),
|
||||||
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
|
||||||
|
|
||||||
|
exports.default = async (req, res) => {
|
||||||
|
const BearerToken = req.headers.authorization;
|
||||||
|
const { payments: paymentsToQuery } = req.body;
|
||||||
|
|
||||||
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
|
headers: {
|
||||||
|
Authorization: BearerToken,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await client
|
||||||
|
.setHeaders({ Authorization: BearerToken })
|
||||||
|
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||||
|
payments: paymentsToQuery,
|
||||||
|
});
|
||||||
|
const { payments } = result;
|
||||||
|
|
||||||
|
const QbXmlToExecute = [];
|
||||||
|
payments.map((i) => {
|
||||||
|
QbXmlToExecute.push({
|
||||||
|
id: i.id,
|
||||||
|
okStatusCodes: ["0"],
|
||||||
|
qbxml: generatePayment(i),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(200).json(QbXmlToExecute);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error", error);
|
||||||
|
res.status(400).send(JSON.stringify(error));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const generatePayment = (payment) => {
|
||||||
|
console.log("generatePayment -> payment", payment);
|
||||||
|
const paymentQbxmlObj = {
|
||||||
|
QBXML: {
|
||||||
|
QBXMLMsgsRq: {
|
||||||
|
"@onError": "continueOnError",
|
||||||
|
ReceivePaymentAddRq: {
|
||||||
|
ReceivePaymentAdd: {
|
||||||
|
CustomerRef: {
|
||||||
|
FullName:
|
||||||
|
payment.job.bodyshop.accountingconfig.tiers === 3
|
||||||
|
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
|
||||||
|
payment.job
|
||||||
|
)}:${generateJobTier(payment.job)}`
|
||||||
|
: `${generateOwnerTier(payment.job)}:${generateJobTier(
|
||||||
|
payment.job
|
||||||
|
)}`,
|
||||||
|
},
|
||||||
|
ARAccountRef: {
|
||||||
|
FullName:
|
||||||
|
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
|
||||||
|
},
|
||||||
|
TxnDate: moment(payment.created_at).format("YYYY-MM-DD"), //Trim String
|
||||||
|
RefNumber: payment.stripeid || payment.transactionid,
|
||||||
|
TotalAmount: Dinero({
|
||||||
|
amount: Math.round(payment.amount * 100),
|
||||||
|
}).toFormat(DineroQbFormat),
|
||||||
|
Memo: `RO ${payment.job.ro_number || ""} OWNER ${
|
||||||
|
payment.job.ownr_fn || ""
|
||||||
|
} ${payment.job.ownr_ln || ""} ${payment.job.ownr_co_nm || ""} ${
|
||||||
|
payment.stripeid
|
||||||
|
}`,
|
||||||
|
IsAutoApply: true,
|
||||||
|
// AppliedToTxnAdd:{
|
||||||
|
// T
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var paymentQbxmlPartial = builder
|
||||||
|
.create(paymentQbxmlObj, {
|
||||||
|
version: "1.30",
|
||||||
|
encoding: "UTF-8",
|
||||||
|
headless: true,
|
||||||
|
})
|
||||||
|
.end({ pretty: true });
|
||||||
|
|
||||||
|
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
|
||||||
|
console.log("generateBill -> paymentQbxmlFull", paymentQbxmlFull);
|
||||||
|
|
||||||
|
return paymentQbxmlFull;
|
||||||
|
};
|
||||||
@@ -12,6 +12,8 @@ require("dotenv").config({
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { generateJobTier, generateOwnerTier, generateSourceTier } = QbXmlUtils;
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
const BearerToken = req.headers.authorization;
|
const BearerToken = req.headers.authorization;
|
||||||
const { jobId } = req.body;
|
const { jobId } = req.body;
|
||||||
@@ -72,224 +74,13 @@ exports.default = async (req, res) => {
|
|||||||
qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop),
|
qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop),
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json([{ id: jobId, okStatusCodes: ["0"], qbxml: t }]);
|
res.status(200).json(QbXmlToExecute);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("error", error);
|
console.log("error", error);
|
||||||
res.status(400).send(JSON.stringify(error));
|
res.status(400).send(JSON.stringify(error));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const t = `<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<?qbxml version="13.0"?>
|
|
||||||
<QBXML>
|
|
||||||
<QBXMLMsgsRq onError="continueOnError">
|
|
||||||
<CustomerAddRq>
|
|
||||||
<CustomerAdd>
|
|
||||||
<Name>Insurance Corporation of British Co</Name>
|
|
||||||
<BillAddress>
|
|
||||||
<Addr1>21291 122B AVE</Addr1>
|
|
||||||
<City>MAPLE RIDGE</City>
|
|
||||||
<State>BC</State>
|
|
||||||
</BillAddress>
|
|
||||||
</CustomerAdd>
|
|
||||||
</CustomerAddRq>
|
|
||||||
|
|
||||||
<CustomerAddRq>
|
|
||||||
<CustomerAdd>
|
|
||||||
<Name>CLOVER LANDON #4</Name>
|
|
||||||
<ParentRef>
|
|
||||||
<FullName>Insurance Corporation of British Co</FullName>
|
|
||||||
</ParentRef>
|
|
||||||
</CustomerAdd>
|
|
||||||
</CustomerAddRq>
|
|
||||||
|
|
||||||
<CustomerAddRq>
|
|
||||||
<CustomerAdd>
|
|
||||||
<Name>RO29</Name>
|
|
||||||
<ParentRef>
|
|
||||||
<FullName>Insurance Corporation of British Co:CLOVER LANDON #4</FullName>
|
|
||||||
</ParentRef>
|
|
||||||
</CustomerAdd>
|
|
||||||
</CustomerAddRq>
|
|
||||||
|
|
||||||
<InvoiceAddRq>
|
|
||||||
<InvoiceAdd>
|
|
||||||
<CustomerRef>
|
|
||||||
<FullName>Insurance Corporation of British Co:CLOVER LANDON #4:RO29</FullName>
|
|
||||||
</CustomerRef>
|
|
||||||
<TxnDate/>
|
|
||||||
<RefNumber>RO29</RefNumber>
|
|
||||||
<BillAddress>
|
|
||||||
<Addr1>21291 122B AVE</Addr1>
|
|
||||||
<City>MAPLE RIDGE</City>
|
|
||||||
<State>BC</State>
|
|
||||||
</BillAddress>
|
|
||||||
<PONumber>BM27914-1-A</PONumber>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_PAA</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>Aftermarketd</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>1351.03</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_PAN</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>BODY SHOP SALES:PARTS:OEM</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>292.45</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_ATP</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>ATPd</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>144.09</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_LAB</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>BODY SHOP SALESLABOR:BODY</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>653.35</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_LAR</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>BODY SHOP SALES:LABOR:REFINISH</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>565.26</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_MAPA</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>paintd</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>347.66</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>BODY SHOP_MASH</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>shopd</Desc>
|
|
||||||
<Quantity>1</Quantity>
|
|
||||||
<Amount>54.38</Amount>
|
|
||||||
<SalesTaxCodeRef>
|
|
||||||
<FullName>E</FullName>
|
|
||||||
</SalesTaxCodeRef>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>GST On Sales</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>Receiver General - GST</Desc>
|
|
||||||
<Amount>170.41</Amount>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
<InvoiceLineAdd>
|
|
||||||
<ItemRef>
|
|
||||||
<FullName>PST On Sales</FullName>
|
|
||||||
</ItemRef>
|
|
||||||
<Desc>Ministry of Finance (BC)</Desc>
|
|
||||||
<Amount>238.58</Amount>
|
|
||||||
</InvoiceLineAdd>
|
|
||||||
</InvoiceAdd>
|
|
||||||
</InvoiceAddRq>
|
|
||||||
</QBXMLMsgsRq>
|
|
||||||
|
|
||||||
</QBXML>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// exports.default = async (req, res) => {
|
|
||||||
// const BearerToken = req.headers.authorization;
|
|
||||||
// const { jobId } = req.body;
|
|
||||||
|
|
||||||
// const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
|
||||||
// headers: {
|
|
||||||
// Authorization: BearerToken,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// const result = await client
|
|
||||||
// .setHeaders({ Authorization: BearerToken })
|
|
||||||
// .request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { id: jobId });
|
|
||||||
// const { jobs_by_pk } = result;
|
|
||||||
// const { bodyshop } = jobs_by_pk;
|
|
||||||
// const QbXmlToExecute = [];
|
|
||||||
|
|
||||||
// //Is this a two tier, or 3 tier setup?
|
|
||||||
// const isThreeTier = bodyshop.accountingconfig.tiers === 3;
|
|
||||||
// const twoTierPref = bodyshop.accountingconfig.twotierpref;
|
|
||||||
|
|
||||||
// if (isThreeTier) {
|
|
||||||
// QbXmlToExecute.push({
|
|
||||||
// id: jobId,
|
|
||||||
// okStatusCodes: ["0", "3100"],
|
|
||||||
// qbxml: generateSourceCustomerQbxml(jobs_by_pk, bodyshop), // Create the source customer.
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// QbXmlToExecute.push({
|
|
||||||
// id: jobId,
|
|
||||||
// okStatusCodes: ["0", "3100"],
|
|
||||||
// qbxml: generateJobQbxml(
|
|
||||||
// jobs_by_pk,
|
|
||||||
// bodyshop,
|
|
||||||
// isThreeTier,
|
|
||||||
// 2,
|
|
||||||
// twoTierPref
|
|
||||||
// ),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// QbXmlToExecute.push({
|
|
||||||
// id: jobId,
|
|
||||||
// okStatusCodes: ["0", "3100"],
|
|
||||||
// qbxml: generateJobQbxml(
|
|
||||||
// jobs_by_pk,
|
|
||||||
// bodyshop,
|
|
||||||
// isThreeTier,
|
|
||||||
// 3,
|
|
||||||
// twoTierPref
|
|
||||||
// ),
|
|
||||||
// });
|
|
||||||
// //Generate the actual invoice.
|
|
||||||
// QbXmlToExecute.push({
|
|
||||||
// id: jobId,
|
|
||||||
// okStatusCodes: ["0"],
|
|
||||||
// qbxml: generateInvoiceQbxml(jobs_by_pk, bodyshop),
|
|
||||||
// });
|
|
||||||
|
|
||||||
// res.status(200).json(QbXmlToExecute);
|
|
||||||
// } catch (error) {
|
|
||||||
// console.log("error", error);
|
|
||||||
// res.status(400).send(JSON.stringify(error));
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
||||||
const customerQbxmlObj = {
|
const customerQbxmlObj = {
|
||||||
QBXML: {
|
QBXML: {
|
||||||
@@ -324,22 +115,6 @@ const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
|||||||
return customerQbxml_Full;
|
return customerQbxml_Full;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateSourceTier = (jobs_by_pk) => {
|
|
||||||
return jobs_by_pk.ins_co_nm;
|
|
||||||
};
|
|
||||||
const generateJobTier = (jobs_by_pk) => {
|
|
||||||
return jobs_by_pk.ro_number;
|
|
||||||
};
|
|
||||||
const generateOwnerTier = (jobs_by_pk) => {
|
|
||||||
return jobs_by_pk.ownr_co_nm
|
|
||||||
? `${jobs_by_pk.ownr_co_nm} - ${jobs_by_pk.ownr_ln || ""} ${
|
|
||||||
jobs_by_pk.ownr_fn || ""
|
|
||||||
} #${jobs_by_pk.owner.accountingid || ""}`
|
|
||||||
: `${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""} #${
|
|
||||||
jobs_by_pk.owner.accountingid || ""
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateJobQbxml = (
|
const generateJobQbxml = (
|
||||||
jobs_by_pk,
|
jobs_by_pk,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
|
|||||||
@@ -4,3 +4,21 @@ exports.addQbxmlHeader = addQbxmlHeader = (xml) => {
|
|||||||
${xml}
|
${xml}
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
exports.generateSourceTier = (jobs_by_pk) => {
|
||||||
|
return jobs_by_pk.ins_co_nm;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.generateJobTier = (jobs_by_pk) => {
|
||||||
|
return jobs_by_pk.ro_number;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.generateOwnerTier = (jobs_by_pk) => {
|
||||||
|
return jobs_by_pk.ownr_co_nm
|
||||||
|
? `${jobs_by_pk.ownr_co_nm} - ${jobs_by_pk.ownr_ln || ""} ${
|
||||||
|
jobs_by_pk.ownr_fn || ""
|
||||||
|
} #${jobs_by_pk.owner.accountingid || ""}`
|
||||||
|
: `${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""} #${
|
||||||
|
jobs_by_pk.owner.accountingid || ""
|
||||||
|
}`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
exports.receivables = require("./qbxml-receivables").default;
|
exports.receivables = require("./qbxml-receivables").default;
|
||||||
exports.payables = require("./qbxml-payables").default;
|
exports.payables = require("./qbxml-payables").default;
|
||||||
|
exports.payments = require("./qbxml-payments").default;
|
||||||
|
|||||||
@@ -106,9 +106,40 @@ query QUERY_INVOICES_FOR_PAYABLES_EXPORT($invoices: [uuid!]!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports.QUERY_PAYMENTS_FOR_EXPORT = `
|
||||||
|
query QUERY_PAYMENTS_FOR_EXPORT($payments: [uuid!]!) {
|
||||||
|
payments(where: {id: {_in: $payments}}) {
|
||||||
|
id
|
||||||
|
created_at
|
||||||
|
jobid
|
||||||
|
job {
|
||||||
|
id
|
||||||
|
ro_number
|
||||||
|
est_number
|
||||||
|
ins_co_nm
|
||||||
|
owner{
|
||||||
|
accountingid
|
||||||
|
}
|
||||||
|
ownr_fn
|
||||||
|
ownr_ln
|
||||||
|
ownr_co_nm
|
||||||
|
bodyshop{
|
||||||
|
accountingconfig
|
||||||
|
md_responsibility_centers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
transactionid
|
||||||
|
memo
|
||||||
|
amount
|
||||||
|
stripeid
|
||||||
|
exportedat
|
||||||
|
stripeid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
exports.QUERY_UPCOMING_APPOINTMENTS = `
|
exports.QUERY_UPCOMING_APPOINTMENTS = `
|
||||||
query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
query QUERY_UPCOMING_APPOINTMENTS($now: timestamptz!, $jobId: uuid!) {
|
||||||
jobs_by_pk(id: $jobId) {
|
jobs_by_pk(id: $jobId) {
|
||||||
|
|||||||
Reference in New Issue
Block a user