Basic IIF rendering with test component for BOD-83

This commit is contained in:
Patrick Fic
2020-05-21 16:38:06 -07:00
parent e906095d97
commit f631b91b18
10 changed files with 143 additions and 133 deletions

View File

@@ -1,143 +1,49 @@
import { Editor } from "@tinymce/tinymce-react";
import axios from "axios";
import React, { useState } from "react";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { EmailSettings } from "../../utils/TemplateConstants";
import {
endLoading,
startLoading,
} from "../../redux/application/application.actions";
import { setEmailOptions } from "../../redux/email/email.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { auth } from "../../firebase/firebase.utils";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
load: () => dispatch(startLoading()),
endload: () => dispatch(endLoading()),
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(
mapStateToProps,
mapDispatchToProps
)(function Test({ setEmailOptions, load, endload, bodyshop }) {
const [state, setState] = useState(temp);
const handleEditorChange = (content, editor) => {
setState(content);
)(function Test({ bodyshop }) {
const handle = async () => {
console.log(
"await auth.currentUser.getIdToken(true)",
await auth.currentUser.getIdToken(true)
);
const response = await axios.post(
"/accounting/iif/receivables",
{ jobId: "661dd1d5-bf06-426f-8bd2-bd9e41de8eb1" },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken(true)}`,
},
}
);
console.log("handle -> result", response);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute(
"download",
response.headers.filename || "receivables.iif"
); //or any other extension
document.body.appendChild(link);
link.click();
};
return (
<div>
<button
onClick={() => {
axios
.post("/render", {
view: state,
context: {
people: ["Yehuda Katz", "Alan Johnson", "Charles Jolley"],
},
})
.then((r) => {
var newWin = window.open(
"url",
"windowName",
"height=300,width=300"
);
newWin.document.write(r.data);
});
}}>
TinyMCE
</button>
<Editor
value={state}
apiKey='f3s2mjsd77ya5qvqkee9vgh612cm6h41e85efqakn2d0kknk'
init={{
height: 500,
//menubar: false,
encoding: "raw",
extended_valid_elements: "span",
plugins: [
"advlist autolink lists link image charmap print preview anchor",
"searchreplace visualblocks code fullscreen",
"insertdatetime media table paste code help wordcount",
],
toolbar:
"undo redo | formatselect | bold italic backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | removeformat | help",
}}
onEditorChange={handleEditorChange}
/>
<button
onClick={() =>
setEmailOptions({
messageOptions: {
to: "patrickwf@gmail.com",
Subject: "TODO FIX ME",
},
template: {
name: "appointment_reminder",
variables: { id: "2b42336f-b8de-4f04-a053-d6bff034d384" },
},
})
}>
Set email config.
</button>
<button
onClick={() =>
setEmailOptions({
messageOptions: {
from: {
name: bodyshop.shopname || EmailSettings.fromNameDefault,
address: EmailSettings.fromAddress,
},
to: "patrickwf@gmail.com",
replyTo: bodyshop.email,
Subject: "TODO FIX ME",
},
template: {
name: "parts_order_confirmation",
variables: { id: "6fea31e9-ea85-4c89-ac56-6f9cc84531fe" },
},
})
}>
Parts Order
</button>
<button onClick={handle}>Hit with Header.</button>
</div>
);
});
const temp = `<div style="font-family: Arial, Helvetica, sans-serif;">
<p style="text-align: center;"><span>&rarr;<span> This is a full-featured editor demo. </span>Please explore! &larr;</span></p>
<p style="text-align: center;">&nbsp;</p>
<h2 style="text-align: center;"><span>TinyMCE is the world's most customizable, and flexible, rich text editor.</span></h2>
<p style="text-align: center;"><span><strong> A featherweight download, TinyMCE can handle any challenge you throw at it. </strong></span></p>
<p style="text-align: center;">&nbsp;</p>
<p>&nbsp;</p>
<table style="border-collapse: collapse; width: 85%; height: 86px; border-color: initial; border-style: solid; margin-left: auto; margin-right: auto;">
<tbody>
<tr style="height: 22px;">
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>🛠 50+ Plugins</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>💡 Premium Support</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>🖍 Custom Skins</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px;"><span>⚙ Full API Access</span></td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px; display: none;"><span>{{#each people}}</span></td>
</tr>
<tr style="height: 22px;">
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
<td style="width: 25%; text-align: center; padding: 7px; height: 22px; border-style: solid; border-width: 1px;"><span>{{this}}</span></td>
</tr>
<tr style="height: 21px; display: none;">
<td style="height: 21px;"><span>{{/each}}</span></td>
</tr>
</tbody>
</table>
</div>`;

View File

@@ -50,7 +50,7 @@ export function JobsCloseSaveButton({
}
setLoading(false);
};
console.log("suspense", suspenseAmount);
return (
<Button
onClick={handleSave}

View File

@@ -576,7 +576,7 @@
"rate_la3": "LA3 Rate",
"rate_la4": "LA4 Rate",
"rate_laa": "Aluminum Rate",
"rate_lab": "Labor Rate",
"rate_lab": "Body Rate",
"rate_lad": "Diagnostic Rate",
"rate_lae": "Electrical Rate",
"rate_laf": "Frame Rate",

View File

@@ -20,6 +20,7 @@
"cloudinary": "^1.21.0",
"compression": "^1.7.4",
"cors": "2.8.5",
"dinero.js": "^1.8.1",
"dotenv": "8.2.0",
"express": "^4.16.4",
"express-sslify": "^1.2.0",

View File

@@ -31,10 +31,18 @@ var sendEmail = require("./sendemail.js");
app.post("/sendemail", sendEmail.sendEmail);
//Test route to ensure Express is responding.
app.get("/test", function (req, res) {
res.status(200).send();
app.get("/test", async function (req, res) {
res.status(200).send("OK");
});
app.post("/test", async function (req, res) {
res.status(200).send("OK");
});
//Invoicing-IIF
const accountingIIF = require("./server/accounting/iif/iif");
app.post("/accounting/iif/receivables", accountingIIF.receivables);
//Cloudinary Media Paths
var media = require("./server/media/media");
app.post("/media/sign", media.createSignedUploadURL);

View File

@@ -0,0 +1,58 @@
const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const queries = require("../../graphql-client/queries");
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 response = [];
response.push(TRNS_HEADER);
response.push(generateInvoiceHeader(result.jobs_by_pk));
response.push(END_TRNS);
res.setHeader("Content-type", "application/octet-stream");
res.setHeader("Content-disposition", "attachment; filename=file.txt");
res.setHeader("filename", `${result.jobs_by_pk.ro_number}-RECEIVABLES.iif`);
res.send(response.join("\n"));
} catch (error) {
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
};
const TRNS_HEADER = `!TRNS TRNSID TRNSTYPE DATE ACCNT NAME CLASS AMOUNT DOCNUM MEMO CLEAR TOPRINT NAMEISTAXABLE ADDR1 ADDR2 ADDR3 ADDR4 DUEDATE TERMS OTHER1 PONUM
!SPL SPLID TRNSTYPE DATE ACCNT NAME CLASS AMOUNT DOCNUM MEMO CLEAR QNTY PRICE INVITEM PAYMETH TAXABLE VALADJ SERVICEDATE OTHER2 EXTRA
!ENDTRNS`;
const generateInvoiceHeader = (job) =>
`TRNS INVOICE ${new Date(job.date_invoiced).getMonth() + 1}/${new Date(
job.date_invoiced
).getDate()}/${new Date(
job.date_invoiced
).getFullYear()} Accounts Receivable GUO DA Acct.# ${job.ownr_id}:${
job.ro_number
} 0100 ${job.clm_total} ${job.ro_number} N N Y GUO DA Acct.# ${job.ownr_id}:${
job.ro_number
} ${job.ownr_addr1} ${job.ownr_city} ${job.ownr_st} ${job.ownr_zip} `;
const generateInvoiceLine = (line) =>
`SPL INVOICE 05/21/2020 Sales:Total Labour:Sales, Body Shop Labour 0100 -572.60 114519 Labor Body N 572.60 Total Labour:4015 Y N `;
const END_TRNS = `ENDTRNS`;

View File

@@ -0,0 +1 @@
exports.receivables = require("./iif-receivables").default

View File

@@ -1,10 +1,17 @@
const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path");
require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || 'development'}`) });
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
//TODO May need to use a different client that includes caching of resources.
//TODO May need to use a different client that includes caching of resources.
exports.client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
"x-hasura-admin-secret": process.env.HASURA_ADMIN_SECRET
}
"x-hasura-admin-secret": process.env.HASURA_ADMIN_SECRET,
},
});
exports.unauthclient = new GraphQLClient(process.env.GRAPHQL_ENDPOINT);

View File

@@ -39,3 +39,27 @@ mutation UPDATE_MESSAGE($msid: String!, $fields: messages_set_input!) {
}
}
`;
exports.QUERY_JOBS_FOR_RECEIVABLES_EXPORT = `
query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($id: uuid!){
jobs_by_pk(id: $id) {
id
job_totals
date_invoiced
ro_number
clm_total
invoice_allocation
ownerid
ownr_addr1
ownr_addr2
ownr_zip
ownr_city
ownr_st
}
bodyshops{
id
md_responsibility_centers
}
}
`;

View File

@@ -803,6 +803,11 @@ dicer@^0.3.0:
dependencies:
streamsearch "0.1.2"
dinero.js@^1.8.1:
version "1.8.1"
resolved "https://registry.yarnpkg.com/dinero.js/-/dinero.js-1.8.1.tgz#775a647629b4195af9d02f46e9b7fa1fd81e906d"
integrity sha512-AQ09MDKonkGUrhBZZFx4tPTVcVJuHJ0VEA73LvcBoBB2eQSi1DbapeXj4wnUUpx1hVnPdyev1xPNnNMGy/Au0g==
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"