diff --git a/posprinter/public/js/pos_override.js b/posprinter/public/js/pos_override.js
index 6c65727..1b0c278 100644
--- a/posprinter/public/js/pos_override.js
+++ b/posprinter/public/js/pos_override.js
@@ -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(`
+
+
${__("Change")}
+
${format_currency(doc.change_amount, doc.currency)}
+
`);
+ }
+
+ 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 = `
+
+
+
+
+
+
+
+ ${resp.message.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);
+ }
+}
+