feature/IO-3052-Skia-Canvas-Handler: Merge release and fix PR's
Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -25,6 +25,9 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
|||||||
<Form.Item label={t("owners.fields.ownr_co_nm")} name="ownr_co_nm">
|
<Form.Item label={t("owners.fields.ownr_co_nm")} name="ownr_co_nm">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("owners.fields.accountingid")} name="accountingid">
|
||||||
|
<Input disabled/>
|
||||||
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow header={t("owners.forms.address")}>
|
<LayoutFormRow header={t("owners.forms.address")}>
|
||||||
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
<Form.Item label={t("owners.fields.ownr_addr1")} name="ownr_addr1">
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export const QUERY_OWNER_BY_ID = gql`
|
|||||||
query QUERY_OWNER_BY_ID($id: uuid!) {
|
query QUERY_OWNER_BY_ID($id: uuid!) {
|
||||||
owners_by_pk(id: $id) {
|
owners_by_pk(id: $id) {
|
||||||
id
|
id
|
||||||
|
accountingid
|
||||||
allow_text_message
|
allow_text_message
|
||||||
ownr_addr1
|
ownr_addr1
|
||||||
ownr_addr2
|
ownr_addr2
|
||||||
|
|||||||
@@ -2394,6 +2394,7 @@
|
|||||||
"selectexistingornew": "Select an existing owner record or create a new one. "
|
"selectexistingornew": "Select an existing owner record or create a new one. "
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"accountingid": "Accounting ID",
|
||||||
"address": "Address",
|
"address": "Address",
|
||||||
"allow_text_message": "Permission to Text?",
|
"allow_text_message": "Permission to Text?",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
@@ -3057,6 +3058,7 @@
|
|||||||
"production_not_production_status": "Production not in Production Status",
|
"production_not_production_status": "Production not in Production Status",
|
||||||
"production_over_time": "Production Level over Time",
|
"production_over_time": "Production Level over Time",
|
||||||
"psr_by_make": "Percent of Sales by Vehicle Make",
|
"psr_by_make": "Percent of Sales by Vehicle Make",
|
||||||
|
"purchase_return_ratio_excel": "Purchase & Return Ratio - Excel",
|
||||||
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
"purchase_return_ratio_grouped_by_vendor_detail": "Purchase & Return Ratio by Vendor (Detail)",
|
||||||
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
|
"purchase_return_ratio_grouped_by_vendor_summary": "Purchase & Return Ratio by Vendor (Summary)",
|
||||||
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
|
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
|
||||||
@@ -3082,6 +3084,7 @@
|
|||||||
"timetickets": "Time Tickets",
|
"timetickets": "Time Tickets",
|
||||||
"timetickets_employee": "Employee Time Tickets",
|
"timetickets_employee": "Employee Time Tickets",
|
||||||
"timetickets_summary": "Time Tickets Summary",
|
"timetickets_summary": "Time Tickets Summary",
|
||||||
|
"total_loss_jobs": "Jobs Marked as Total Loss",
|
||||||
"unclaimed_hrs": "Unflagged Hours",
|
"unclaimed_hrs": "Unflagged Hours",
|
||||||
"void_ros": "Void ROs",
|
"void_ros": "Void ROs",
|
||||||
"work_in_progress_committed_labour": "Work in Progress - Committed Labor",
|
"work_in_progress_committed_labour": "Work in Progress - Committed Labor",
|
||||||
|
|||||||
@@ -2394,6 +2394,7 @@
|
|||||||
"selectexistingornew": ""
|
"selectexistingornew": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"accountingid": "",
|
||||||
"address": "Dirección",
|
"address": "Dirección",
|
||||||
"allow_text_message": "Permiso de texto?",
|
"allow_text_message": "Permiso de texto?",
|
||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
@@ -3057,6 +3058,7 @@
|
|||||||
"production_not_production_status": "",
|
"production_not_production_status": "",
|
||||||
"production_over_time": "",
|
"production_over_time": "",
|
||||||
"psr_by_make": "",
|
"psr_by_make": "",
|
||||||
|
"purchase_return_ratio_excel": "",
|
||||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||||
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
||||||
"purchases_by_cost_center_detail": "",
|
"purchases_by_cost_center_detail": "",
|
||||||
@@ -3082,6 +3084,7 @@
|
|||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
"timetickets_employee": "",
|
"timetickets_employee": "",
|
||||||
"timetickets_summary": "",
|
"timetickets_summary": "",
|
||||||
|
"total_loss_jobs": "",
|
||||||
"unclaimed_hrs": "",
|
"unclaimed_hrs": "",
|
||||||
"void_ros": "",
|
"void_ros": "",
|
||||||
"work_in_progress_committed_labour": "",
|
"work_in_progress_committed_labour": "",
|
||||||
|
|||||||
@@ -2394,6 +2394,7 @@
|
|||||||
"selectexistingornew": ""
|
"selectexistingornew": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"accountingid": "",
|
||||||
"address": "Adresse",
|
"address": "Adresse",
|
||||||
"allow_text_message": "Autorisation de texte?",
|
"allow_text_message": "Autorisation de texte?",
|
||||||
"name": "Prénom",
|
"name": "Prénom",
|
||||||
@@ -3057,6 +3058,7 @@
|
|||||||
"production_not_production_status": "",
|
"production_not_production_status": "",
|
||||||
"production_over_time": "",
|
"production_over_time": "",
|
||||||
"psr_by_make": "",
|
"psr_by_make": "",
|
||||||
|
"purchase_return_ratio_excel": "",
|
||||||
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
"purchase_return_ratio_grouped_by_vendor_detail": "",
|
||||||
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
"purchase_return_ratio_grouped_by_vendor_summary": "",
|
||||||
"purchases_by_cost_center_detail": "",
|
"purchases_by_cost_center_detail": "",
|
||||||
@@ -3082,6 +3084,7 @@
|
|||||||
"timetickets": "",
|
"timetickets": "",
|
||||||
"timetickets_employee": "",
|
"timetickets_employee": "",
|
||||||
"timetickets_summary": "",
|
"timetickets_summary": "",
|
||||||
|
"total_loss_jobs": "",
|
||||||
"unclaimed_hrs": "",
|
"unclaimed_hrs": "",
|
||||||
"void_ros": "",
|
"void_ros": "",
|
||||||
"work_in_progress_committed_labour": "",
|
"work_in_progress_committed_labour": "",
|
||||||
|
|||||||
@@ -2184,6 +2184,30 @@ export const TemplateList = (type, context) => {
|
|||||||
},
|
},
|
||||||
group: "payroll",
|
group: "payroll",
|
||||||
adp_payroll: true
|
adp_payroll: true
|
||||||
|
},
|
||||||
|
purchase_return_ratio_excel: {
|
||||||
|
title: i18n.t("reportcenter.templates.purchase_return_ratio_excel"),
|
||||||
|
subject: i18n.t("reportcenter.templates.purchase_return_ratio_excel"),
|
||||||
|
key: "purchase_return_ratio_excel",
|
||||||
|
//idtype: "vendor",
|
||||||
|
reporttype: "excel",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.bills"),
|
||||||
|
field: i18n.t("bills.fields.date")
|
||||||
|
},
|
||||||
|
group: "purchases"
|
||||||
|
},
|
||||||
|
total_loss_jobs: {
|
||||||
|
title: i18n.t("reportcenter.templates.total_loss_jobs"),
|
||||||
|
subject: i18n.t("reportcenter.templates.total_loss_jobs"),
|
||||||
|
key: "total_loss_jobs",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||||
|
field: i18n.t("jobs.fields.date_open")
|
||||||
|
},
|
||||||
|
group: "jobs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
@@ -328,6 +328,7 @@ async function InsertOwner(oauthClient, qbo_realmId, req, job, isThreeTier, pare
|
|||||||
PostalCode: job.ownr_zip,
|
PostalCode: job.ownr_zip,
|
||||||
CountrySubDivisionCode: job.ownr_st
|
CountrySubDivisionCode: job.ownr_st
|
||||||
},
|
},
|
||||||
|
...(job.ownr_ea ? { BillEmail: { Address: job.ownr_ea.trim() } } : {}),
|
||||||
...(isThreeTier
|
...(isThreeTier
|
||||||
? {
|
? {
|
||||||
Job: true,
|
Job: true,
|
||||||
@@ -395,7 +396,7 @@ async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
|||||||
PostalCode: job.ownr_zip,
|
PostalCode: job.ownr_zip,
|
||||||
CountrySubDivisionCode: job.ownr_st
|
CountrySubDivisionCode: job.ownr_st
|
||||||
},
|
},
|
||||||
|
...(job.ownr_ea ? { BillEmail: { Address: job.ownr_ea.trim() } } : {}),
|
||||||
Job: true,
|
Job: true,
|
||||||
ParentRef: {
|
ParentRef: {
|
||||||
value: parentTierRef.Id
|
value: parentTierRef.Id
|
||||||
@@ -556,7 +557,8 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
|
|||||||
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(),
|
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(),
|
||||||
Line2: job.ownr_addr1 || "",
|
Line2: job.ownr_addr1 || "",
|
||||||
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`
|
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`
|
||||||
}
|
},
|
||||||
|
...(job.ownr_ea ? { BillEmail: { Address: job.ownr_ea.trim() } } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
||||||
@@ -673,7 +675,8 @@ async function InsertInvoiceMultiPayerInvoice(
|
|||||||
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(),
|
Line3: `${job.ownr_city || ""}, ${job.ownr_st || ""} ${job.ownr_zip || ""}`.trim(),
|
||||||
Line2: job.ownr_addr1 || "",
|
Line2: job.ownr_addr1 || "",
|
||||||
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`
|
Line1: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${job.ownr_co_nm || ""}`
|
||||||
}
|
},
|
||||||
|
...(job.ownr_ea ? { BillEmail: { Address: job.ownr_ea.trim() } } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ const Chart = require("chart.js/auto");
|
|||||||
const { backgroundColors, borderColors } = require("./canvas-colors");
|
const { backgroundColors, borderColors } = require("./canvas-colors");
|
||||||
const { defaultsDeep, isNumber } = require("lodash");
|
const { defaultsDeep, isNumber } = require("lodash");
|
||||||
|
|
||||||
|
let isProcessing = false;
|
||||||
|
const requestQueue = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FontLibrary.use("Montserrat", [
|
FontLibrary.use("Montserrat", [
|
||||||
"/usr/share/fonts/Montserrat-Regular.ttf",
|
"/usr/share/fonts/Montserrat-Regular.ttf",
|
||||||
@@ -60,69 +63,81 @@ const getChartConfiguration = (keys, values, override) => {
|
|||||||
return defaultsDeep(override || {}, defaultConfiguration);
|
return defaultsDeep(override || {}, defaultConfiguration);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.canvastest = function (req, res) {
|
const processCanvasRequest = async (req, res, isSkia = false) => {
|
||||||
res.status(200).send("OK");
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.canvas = function (req, res) {
|
|
||||||
const { logger } = req;
|
|
||||||
|
|
||||||
const { w, h, values, keys, override } = req.body;
|
|
||||||
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
|
||||||
|
|
||||||
// Set the default Width and Height
|
|
||||||
let [width, height] = [500, 275];
|
|
||||||
|
|
||||||
// Allow for custom width and height
|
|
||||||
if (isNumber(w)) {
|
|
||||||
width = w;
|
|
||||||
}
|
|
||||||
if (isNumber(h)) {
|
|
||||||
height = h;
|
|
||||||
}
|
|
||||||
|
|
||||||
const configuration = getChartConfiguration(keys, values, override);
|
|
||||||
|
|
||||||
res.status(200).send(
|
|
||||||
(() => {
|
|
||||||
const canvas = createCanvas(width, height);
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
new Chart(ctx, configuration);
|
|
||||||
return canvas.toDataURL();
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.canvasSkia = async function (req, res) {
|
|
||||||
const { logger } = req;
|
const { logger } = req;
|
||||||
const { w, h, values, keys, override } = req.body;
|
const { w, h, values, keys, override } = req.body;
|
||||||
|
|
||||||
// Log incoming request for debugging
|
|
||||||
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
logger.log("inbound-canvas-creation", "debug", "jsr", null, { w, h, values, keys, override });
|
||||||
|
|
||||||
// Default width and height
|
// Default width and height
|
||||||
const width = typeof w === "number" && w > 0 ? w : 500;
|
const width = isNumber(w) && w > 0 ? w : 500;
|
||||||
const height = typeof h === "number" && h > 0 ? h : 275;
|
const height = isNumber(h) && h > 0 ? h : 275;
|
||||||
|
|
||||||
const configuration = getChartConfiguration(keys, values, override);
|
const configuration = getChartConfiguration(keys, values, override);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Create a canvas and get the 2D rendering context
|
const canvas = isSkia ? new Canvas(width, height) : createCanvas(width, height);
|
||||||
const canvas = new Canvas(width, height);
|
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
// Render the chart
|
// Render the chart
|
||||||
new Chart(ctx, configuration);
|
const chart = new Chart(ctx, configuration);
|
||||||
|
|
||||||
// Convert the canvas to a Base64-encoded image
|
// Generate and send the image
|
||||||
const chartImage = (await canvas.toBuffer("image/png")).toString("base64");
|
const chartImage = isSkia
|
||||||
const dataURL = `data:image/png;base64,${chartImage}`;
|
? (await canvas.toBuffer("image/png")).toString("base64")
|
||||||
|
: canvas.toDataURL();
|
||||||
|
|
||||||
// Send the Base64-encoded image as the response
|
chart.destroy();
|
||||||
res.status(200).send(dataURL);
|
res.status(200).send(isSkia ? `data:image/png;base64,${chartImage}` : chartImage);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log and handle rendering errors
|
|
||||||
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
logger.log("canvas-error", "error", "jsr", null, { error: error.message });
|
||||||
res.status(500).send("Failed to generate canvas.");
|
res.status(500).send("Failed to generate canvas.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const processNextInQueue = async () => {
|
||||||
|
if (requestQueue.length === 0) {
|
||||||
|
isProcessing = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { req, res, isSkia } = requestQueue.shift();
|
||||||
|
await processCanvasRequest(req, res, isSkia);
|
||||||
|
processNextInQueue();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.canvastest = function (req, res) {
|
||||||
|
res.status(200).send("OK");
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.canvas = async function (req, res) {
|
||||||
|
if (isProcessing) {
|
||||||
|
if (requestQueue.length >= 100) {
|
||||||
|
// Set a maximum queue size
|
||||||
|
return res.status(503).send("Server is busy. Please try again later.");
|
||||||
|
}
|
||||||
|
requestQueue.push({ req, res, isSkia: false });
|
||||||
|
req.logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessing = true;
|
||||||
|
await processCanvasRequest(req, res, false);
|
||||||
|
processNextInQueue();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.canvasSkia = async function (req, res) {
|
||||||
|
if (isProcessing) {
|
||||||
|
if (requestQueue.length >= 100) {
|
||||||
|
// Set a maximum queue size
|
||||||
|
return res.status(503).send("Server is busy. Please try again later.");
|
||||||
|
}
|
||||||
|
requestQueue.push({ req, res, isSkia: true });
|
||||||
|
req.logger.log("inbound-canvas-creation-queue", "debug", "jsr", null, { queue: requestQueue.length });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isProcessing = true;
|
||||||
|
await processCanvasRequest(req, res, true);
|
||||||
|
processNextInQueue();
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user