add support printer TM-T82X-II
This commit is contained in:
@@ -1,13 +1,311 @@
|
||||
frappe.after_ajax(() => {
|
||||
console.log("overriding ====");
|
||||
import html2canvas from 'html2canvas';
|
||||
|
||||
const original = erpnext.PointOfSale.Controller.prototype.constuctor;
|
||||
frappe.router.on("change", () => {
|
||||
const route = frappe.get_route();
|
||||
if (route[0] === "point-of-sale") {
|
||||
waitForPOS(() => {
|
||||
const controller = erpnext.PointOfSale.Controller.prototype.prepare_menu;
|
||||
erpnext.PointOfSale.Controller.prototype.prepare_menu = function() {
|
||||
controller.call(this, ...arguments);
|
||||
this.page.add_menu_item(__("Settup USB Printer"), this.setup_printer.bind(this), false, "Ctrl+P");
|
||||
};
|
||||
|
||||
erpnext.PointOfSale.Controller.prototype.constuctor = function () {
|
||||
console.log("Yoooooooooooooooooow");
|
||||
erpnext.PointOfSale.Controller.prototype.setup_printer = function() {
|
||||
|
||||
original.call(this, ...arguments);
|
||||
if (!navigator.usb) {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: __("Your browser doesn't support WebUSB, please switch to Chromium-based browser"),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("byeeeeeeeeeeeeeeeeee");
|
||||
};
|
||||
try {
|
||||
navigator.usb.requestDevice({ filters: [] }).then(device => {
|
||||
device.open().then(() => {
|
||||
if (device.configuration === null) {
|
||||
device.selectConfiguration(1);
|
||||
}
|
||||
}).then(() => {
|
||||
let interfaceNum = null, endpointNum = null;
|
||||
for (const iface of device.configuration.interfaces) {
|
||||
for (const alt of iface.alternates) {
|
||||
for (const ep of alt.endpoints) {
|
||||
if (ep.direction === 'out' && ep.type === 'bulk') {
|
||||
interfaceNum = iface.interfaceNumber;
|
||||
endpointNum = ep.endpointNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endpointNum === null) throw new Error('No bulk OUT endpoint found');
|
||||
|
||||
device.claimInterface(0);
|
||||
|
||||
this.device = device;
|
||||
this.device._enpointNum = endpointNum;
|
||||
|
||||
this.order_summary.events.get_device = () => {
|
||||
return this.device;
|
||||
};
|
||||
|
||||
frappe.show_alert({
|
||||
indicator: "green",
|
||||
message: __("Printer connected successfully!"),
|
||||
});
|
||||
|
||||
this.page.add_button(__("Printer Connected"), null, { btn_class: "btn-disabled" });
|
||||
|
||||
}).catch(err => {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: __(err),
|
||||
});
|
||||
});
|
||||
}).catch(err => {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: __(err),
|
||||
});
|
||||
});
|
||||
|
||||
} catch (e) {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: __(e),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
erpnext.PointOfSale.PastOrderSummary.prototype.attach_change_info = function(doc) {
|
||||
if (doc.change_amount == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$payment_container.append(`
|
||||
<div class="summary-row-wrapper payments">
|
||||
<div>${__("Change")}</div>
|
||||
<div>${format_currency(doc.change_amount, doc.currency)}</div>
|
||||
</div>`);
|
||||
}
|
||||
|
||||
erpnext.PointOfSale.PastOrderSummary.prototype.open_cash_drawer = function(doc) {
|
||||
if (doc.change_amount == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const CMD = get_command();
|
||||
const device = this.events.get_device();
|
||||
const open_drawer_command = new Uint8Array(CMD.open_drawer);
|
||||
|
||||
device.transferOut(device._enpointNum, open_drawer_command);
|
||||
}
|
||||
|
||||
const origin_load_summary_of = erpnext.PointOfSale.PastOrderSummary.prototype.load_summary_of;
|
||||
erpnext.PointOfSale.PastOrderSummary.prototype.load_summary_of = function(doc, after_submission = false) {
|
||||
origin_load_summary_of.apply(this, arguments);
|
||||
|
||||
this.attach_change_info(doc);
|
||||
|
||||
this.open_cash_drawer(doc);
|
||||
}
|
||||
|
||||
erpnext.PointOfSale.PastOrderSummary.prototype.custom_print_receipt = function() {
|
||||
const frm = this.events.get_frm();
|
||||
|
||||
frappe.call({
|
||||
method: "frappe.www.printview.get_html_and_style",
|
||||
args: {
|
||||
doc: this.doc,
|
||||
print_format: frm.pos_print_format,
|
||||
letter_head: this.doc.letter_head,
|
||||
lang: this.doc.language || frappe.boot.lang,
|
||||
},
|
||||
}).then(resp => {
|
||||
let base_url = frappe.urllib.get_base_url();
|
||||
|
||||
let print_css = frappe.assets.bundled_asset(
|
||||
"print.bundle.css",
|
||||
frappe.utils.is_rtl(this.lang_code)
|
||||
);
|
||||
|
||||
const fullHtml = `
|
||||
<html>
|
||||
<head>
|
||||
<style>${resp.message.style}</style>
|
||||
<link href="${base_url}${print_css}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="print-format print-format-preview" style="display: flex; flex-direction: column;">
|
||||
${resp.message.html}
|
||||
</div>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
const device = this.events.get_device()
|
||||
|
||||
const canvas = buildCanvas(fullHtml).then(canvas => {
|
||||
const {out, width, height} = canvas2ByteArray(canvas, 150);
|
||||
|
||||
const data = buildImagePrintData(out, width, height);
|
||||
|
||||
device.transferOut(device._enpointNum, data);
|
||||
});
|
||||
}).catch(err => {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: __(`Error getting print format: ${err}`),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const origin_print_receipt = erpnext.PointOfSale.PastOrderSummary.prototype.print_receipt;
|
||||
erpnext.PointOfSale.PastOrderSummary.prototype.print_receipt = function() {
|
||||
if (typeof this.events?.get_device === 'function') {
|
||||
this.custom_print_receipt();
|
||||
return
|
||||
}
|
||||
|
||||
origin_print_receipt.call(this, ...arguments);
|
||||
};
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function get_command() {
|
||||
const ESC = 0x1B;
|
||||
const GS = 0x1D;
|
||||
const BYTE_MASK = 0xFF;
|
||||
const MSB_BIT = 0x80;
|
||||
|
||||
const CMD = {
|
||||
byte_mask: BYTE_MASK,
|
||||
msb_bit: MSB_BIT,
|
||||
init: [ESC, 0x40],
|
||||
bold_on: [ESC, 0x45, 1],
|
||||
bold_off: [ESC, 0x45, 0],
|
||||
center: [ESC, 0x61, 1],
|
||||
left: [ESC, 0x61, 0],
|
||||
dbl_height: [GS, 0x21, 0x11],
|
||||
normal_size: [GS, 0x21, 0x00],
|
||||
feed3: [ESC, 0x64, 3],
|
||||
cut: [GS, 0x56, 0x41, 0x00],
|
||||
lf: [0x0A],
|
||||
open_drawer: [0x1B, 0x70, 0x00, 0x19, 0xFA],
|
||||
print_image: (xL, xH, yL, yH) => [GS, 0x76, 0x30, 0x00, xL, xH, yL, yH],
|
||||
};
|
||||
|
||||
return CMD;
|
||||
}
|
||||
|
||||
function buildImagePrintData(out, w, h) {
|
||||
const CMD = get_command();
|
||||
|
||||
const BITS_PER_BYTE = 8;
|
||||
|
||||
// Width must be multiple of BITS_PER_BYTE
|
||||
const wBytes = Math.ceil(w / BITS_PER_BYTE);
|
||||
const wAligned = wBytes * BITS_PER_BYTE;
|
||||
|
||||
const parts = [];
|
||||
const add = (bytes) => parts.push(new Uint8Array(bytes));
|
||||
|
||||
add(CMD.init);
|
||||
add(CMD.center);
|
||||
|
||||
// GS v 0: xL xH yL yH [data]
|
||||
// xL/xH = bytes per row (little-endian), yL/yH = number of rows
|
||||
const xL = wBytes & CMD.byte_mask, xH = (wBytes >> BITS_PER_BYTE) & CMD.byte_mask;
|
||||
const yL = h & CMD.byte_mask, yH = (h >> BITS_PER_BYTE) & CMD.byte_mask;
|
||||
|
||||
add(CMD.print_image(xL, xH, yL, yH));
|
||||
|
||||
// Pack bits: 1=black, MSB first
|
||||
const rowData = new Uint8Array(wBytes * h);
|
||||
for (let y = 0; y < h; y++) {
|
||||
for (let bx = 0; bx < wBytes; bx++) {
|
||||
let byte = 0;
|
||||
for (let bit = 0; bit < BITS_PER_BYTE; bit++) {
|
||||
const x = bx * BITS_PER_BYTE + bit;
|
||||
if (x < w && out[y * w + x]) byte |= (CMD.msb_bit >> bit);
|
||||
}
|
||||
rowData[y * wBytes + bx] = byte;
|
||||
}
|
||||
}
|
||||
parts.push(rowData);
|
||||
|
||||
add(CMD.left);
|
||||
add(CMD.feed3);
|
||||
add(CMD.cut);
|
||||
|
||||
const total = parts.reduce((s, p) => s + p.length, 0);
|
||||
const buf = new Uint8Array(total);
|
||||
let offset = 0;
|
||||
for (const p of parts) { buf.set(p, offset); offset += p.length; }
|
||||
return buf;
|
||||
}
|
||||
|
||||
function buildCanvas (fullHtml) {
|
||||
return new Promise((resolve) => {
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.style.width = "100%";
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
iframe.onload = () => {
|
||||
const body = iframe.contentDocument.body;
|
||||
const content = iframe.contentDocument.querySelector(".print-format");
|
||||
|
||||
html2canvas(content || body, { scale: 1, useCORS: true, }).then(canvas => {
|
||||
document.body.removeChild(iframe);
|
||||
resolve(canvas);
|
||||
}).catch(err => {
|
||||
frappe.show_alert({
|
||||
indicator: "red",
|
||||
message: __(`Error building canvas: ${err}`),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
iframe.contentDocument.open();
|
||||
iframe.contentDocument.write(fullHtml);
|
||||
iframe.contentDocument.close();
|
||||
});
|
||||
}
|
||||
|
||||
function canvas2ByteArray(canvas, threshold) {
|
||||
const w = canvas.width, h = canvas.height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
const imgData = ctx.getImageData(0, 0, w, h);
|
||||
const pixels = imgData.data;
|
||||
|
||||
const gray = new Float32Array(w * h);
|
||||
for (let i = 0; i < gray.length; i++) {
|
||||
const r = pixels[i*4], g = pixels[i*4+1], b = pixels[i*4+2];
|
||||
gray[i] = 0.299*r + 0.587*g + 0.114*b;
|
||||
}
|
||||
|
||||
const out = new Uint8Array(w * h);
|
||||
|
||||
for (let i = 0; i < gray.length; i++) out[i] = gray[i] < threshold ? 1 : 0;
|
||||
|
||||
return { out, width: w, height: h };
|
||||
}
|
||||
|
||||
function waitForPOS(callback) {
|
||||
if (
|
||||
typeof erpnext !== "undefined" &&
|
||||
erpnext.PointOfSale &&
|
||||
erpnext.PointOfSale.Controller &&
|
||||
erpnext.PointOfSale.PastOrderSummary
|
||||
) {
|
||||
if (typeof erpnext.PointOfSale?.Controller?.prototype?.setup_printer === 'function') {
|
||||
return
|
||||
}
|
||||
callback();
|
||||
} else {
|
||||
setTimeout(() => waitForPOS(callback), 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user