Factura A, B, C, QR
This commit is contained in:
418
index.js
418
index.js
@@ -10,11 +10,17 @@
|
||||
"nodemon": "^3.1.10"
|
||||
}
|
||||
*/
|
||||
|
||||
// || nvm -> nvm install 16.20.2
|
||||
|
||||
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
|
||||
import printer from '@thiagoelg/node-printer'
|
||||
import iconv from "iconv-lite";
|
||||
import QRCode from 'qrcode'
|
||||
import { createCanvas, loadImage } from 'canvas'
|
||||
// Genera un QR y lo imprime en la impresora térmica
|
||||
|
||||
const ESC = "\x1B"
|
||||
const GS = "\x1D"
|
||||
@@ -26,19 +32,10 @@ function line(n = 1) { return "\n".repeat(n); }
|
||||
function boldOn() { return ESC + "E" + "\x01"; }
|
||||
function boldOff() { return ESC + "E" + "\x00"; }
|
||||
function center() { return ESC + "a" + "\x01"; }
|
||||
function centerOff() { return ESC + "a" + "\x00"; }
|
||||
function left() { return ESC + "a" + "\x00"; }
|
||||
function right() { return ESC + "a" + "\x02"; }
|
||||
|
||||
function printTicketDataRaw(data) {
|
||||
printer.printDirect({
|
||||
data,
|
||||
printer: defaultPrinter,
|
||||
type: "RAW",
|
||||
success: jobID => console.log("Trabajo enviado:", jobID),
|
||||
error: err => console.error("Error:", err)
|
||||
})
|
||||
}
|
||||
|
||||
const app = express()
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
@@ -56,6 +53,90 @@ app.post('/digitalpowerstock/ticket/venta', (req, res) => {
|
||||
res.send(200)
|
||||
})
|
||||
|
||||
app.post('/facturador/ticket/a', (req, res) => {
|
||||
const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal } = req.body;
|
||||
ticketFacturadorA(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal)
|
||||
res.send(200)
|
||||
})
|
||||
|
||||
app.post('/facturador/ticket/b', (req, res) => {
|
||||
const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total } = req.body;
|
||||
ticketFacturadorB(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
||||
)
|
||||
res.send(200)
|
||||
})
|
||||
|
||||
app.post('/facturador/ticket/c', (req, res) => {
|
||||
const { comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total } = req.body;
|
||||
ticketFacturadorC(comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
||||
)
|
||||
res.send(200)
|
||||
})
|
||||
|
||||
async function printQRAsImage(link, moduleSize = 6) {
|
||||
try {
|
||||
const qrSize = moduleSize * 33;
|
||||
|
||||
const qrBuffer = await QRCode.toBuffer(link, {
|
||||
width: qrSize,
|
||||
margin: 2,
|
||||
errorCorrectionLevel: 'M',
|
||||
type: 'png'
|
||||
});
|
||||
|
||||
const img = await loadImage(qrBuffer);
|
||||
const canvas = createCanvas(img.width, img.height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const width = canvas.width;
|
||||
const height = canvas.height;
|
||||
const widthBytes = Math.ceil(width / 8);
|
||||
|
||||
const imageBytes = [];
|
||||
|
||||
for (let y = 0; y < height; y++) {
|
||||
for (let x = 0; x < widthBytes; x++) {
|
||||
let byte = 0;
|
||||
for (let bit = 0; bit < 8; bit++) {
|
||||
const pixelX = x * 8 + bit;
|
||||
if (pixelX < width) {
|
||||
const idx = (y * width + pixelX) * 4;
|
||||
const brightness = imageData.data[idx];
|
||||
if (brightness < 128) {
|
||||
byte |= (1 << (7 - bit));
|
||||
}
|
||||
}
|
||||
}
|
||||
imageBytes.push(byte);
|
||||
}
|
||||
}
|
||||
|
||||
const commands = [];
|
||||
|
||||
// Centrar
|
||||
commands.push(Buffer.from([0x1B, 0x61, 0x01]));
|
||||
|
||||
// GS v 0
|
||||
const xL = widthBytes & 0xFF;
|
||||
const xH = (widthBytes >> 8) & 0xFF;
|
||||
const yL = height & 0xFF;
|
||||
const yH = (height >> 8) & 0xFF;
|
||||
|
||||
commands.push(Buffer.from([0x1D, 0x76, 0x30, 0x00, xL, xH, yL, yH]));
|
||||
commands.push(Buffer.from(imageBytes));
|
||||
|
||||
commands.push(Buffer.from([0x0A, 0x0A]));
|
||||
commands.push(Buffer.from([0x1B, 0x61, 0x00]));
|
||||
|
||||
return Buffer.concat(commands);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error generando QR:', error);
|
||||
return Buffer.from('');
|
||||
}
|
||||
}
|
||||
|
||||
export function removeAccents(str) {
|
||||
return String(str)
|
||||
@@ -63,6 +144,72 @@ export function removeAccents(str) {
|
||||
.replace(/[\u0300-\u036f]/g, ''); // elimina marcas diacríticas
|
||||
}
|
||||
|
||||
function printDoubleLine() {
|
||||
return left() + "============================================" + line();
|
||||
}
|
||||
|
||||
function printDottedLine() {
|
||||
return left() + "--------------------------------------------" + line();
|
||||
}
|
||||
|
||||
function printProducts(products) {
|
||||
let ticket = "";
|
||||
const lineWidth = 46; // Ajusta según tu impresora (32, 42, 48 caracteres típicos)
|
||||
const priceWidth = 10; // Espacio reservado para el precio (ej: "$999.99")
|
||||
|
||||
for (const producto of products) {
|
||||
const nombre = removeAccents(producto?.nombre);
|
||||
const cantidad = producto?.cantidad;
|
||||
const precio = `$${producto?.precio}`;
|
||||
|
||||
// Construir la parte izquierda: "2 x Producto"
|
||||
let leftPart = `${cantidad} x ${nombre}`;
|
||||
|
||||
// Calcular cuántos espacios necesitamos
|
||||
const availableSpace = lineWidth - precio.length;
|
||||
|
||||
// Si el nombre es muy largo, cortarlo
|
||||
if (leftPart.length > availableSpace) {
|
||||
leftPart = leftPart.substring(0, availableSpace - 3) + "...";
|
||||
}
|
||||
|
||||
// Rellenar con espacios hasta que el precio quede alineado a la derecha
|
||||
const spaces = lineWidth - leftPart.length - precio.length;
|
||||
const spacePadding = " ".repeat(Math.max(0, spaces));
|
||||
|
||||
ticket += left() + leftPart + spacePadding + precio + line();
|
||||
|
||||
if (producto?.observaciones?.length > 0) {
|
||||
const obs = removeAccents(producto.observaciones);
|
||||
ticket += "Observaciones: " + obs + line();
|
||||
}
|
||||
ticket += printDottedLine();
|
||||
}
|
||||
|
||||
return ticket;
|
||||
}
|
||||
|
||||
function printBetween(leftText, rightText, lineWidth = 46) {
|
||||
// Asegurar que los valores sean strings
|
||||
const left = String(leftText);
|
||||
const right = String(rightText);
|
||||
|
||||
// Calcular el espacio disponible para el texto izquierdo
|
||||
const availableSpace = lineWidth - right.length;
|
||||
|
||||
// Si el texto izquierdo es muy largo, cortarlo
|
||||
let finalLeft = left;
|
||||
if (left.length > availableSpace) {
|
||||
finalLeft = left.substring(0, availableSpace - 3) + "...";
|
||||
}
|
||||
|
||||
// Calcular espacios de relleno
|
||||
const spaces = lineWidth - finalLeft.length - right.length;
|
||||
const spacePadding = " ".repeat(Math.max(0, spaces));
|
||||
|
||||
// Retornar la línea formateada
|
||||
return finalLeft + spacePadding + right;
|
||||
}
|
||||
|
||||
async function ticketDPSTock(
|
||||
nombre, direccion, cp, localidad, provincia, pais,
|
||||
@@ -92,23 +239,8 @@ async function ticketDPSTock(
|
||||
ticket += `Fecha: ${fecha}` + line();
|
||||
ticket += `Vendedor: ${vendedor}` + line(2);
|
||||
|
||||
// Productos
|
||||
const max = 20; // ancho máximo de nombre antes de cortar
|
||||
for (const producto of products) {
|
||||
const nombre = removeAccents(producto.nombre);
|
||||
|
||||
if (producto.nombre.length > max) {
|
||||
ticket += left() + `${producto.cantidad} x ${nombre}` + line();
|
||||
ticket += right() + `$${producto.precio}` + line();
|
||||
} else {
|
||||
ticket += left() + `${producto.cantidad} x ${nombre} - $${producto.precio}` + line();
|
||||
}
|
||||
|
||||
if (producto.observacion) {
|
||||
ticket += left() + `Observacion: ${producto.observacion}` + line();
|
||||
}
|
||||
ticket += line();
|
||||
}
|
||||
ticket += printProducts(JSON.parse(productos))
|
||||
ticket += line();
|
||||
|
||||
// Total
|
||||
ticket += line(2);
|
||||
@@ -134,6 +266,236 @@ async function ticketDPSTock(
|
||||
}
|
||||
}
|
||||
|
||||
async function ticketFacturadorA(
|
||||
comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, localidad, provincia, comprobante, cliente, fecha, qr_link, productos, total, subtotal
|
||||
) {
|
||||
|
||||
try {
|
||||
let ticket = "";
|
||||
|
||||
// Encabezado
|
||||
ticket += init();
|
||||
ticket += center();
|
||||
ticket += boldOn() + comercio + boldOff() + line(2);
|
||||
ticket += centerOff();
|
||||
|
||||
ticket += nombre + line();
|
||||
ticket += "CUIT: Nro: " + comercio_cuit + line();
|
||||
ticket += "Ing. Brutos: " + comercio_cuit + line();
|
||||
ticket += "Direccion: " + direccion + line();
|
||||
ticket += `${localidad} (${provincia})` + line();
|
||||
ticket += "Inicio de Actividades: " + inicio_actividades + line();
|
||||
ticket += condicion_iva_comercio + line();
|
||||
ticket += "PUNTO DE VENTA: " + punto_venta + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += "TICKET / FACTURA A" + line();
|
||||
ticket += "Nro: " + comprobante + line();
|
||||
ticket += "Fecha: " + fecha + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += "TIPO CLIENTE: " + cliente_condicion + line();
|
||||
ticket += "CLIENTE: " + cliente + line();
|
||||
ticket += "CUIT: " + cliente_cuit + line();
|
||||
ticket += "DIRECCION: " + cliente_direccion + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += printProducts(JSON.parse(productos)) + line()
|
||||
|
||||
// Total
|
||||
ticket += printBetween("SUBTOTAL. IMP. NETO GRAVADO: ", `$${subtotal}`) + line();
|
||||
ticket += printBetween("IVA: ", `$${iva_contenido}`) + line();
|
||||
ticket += printBetween("Total: ", `$${total}`) + line(2);
|
||||
|
||||
ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
|
||||
ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
|
||||
ticket += "CAE: " + cae + line()
|
||||
ticket += "Vencimiento CAE: " + vencimiento_cae + line()
|
||||
|
||||
ticket += line(2) + center()
|
||||
|
||||
// Convertir texto a CP850 ANTES del QR
|
||||
const textData = iconv.encode(ticket, "CP850");
|
||||
|
||||
// Generar QR como Buffer binario
|
||||
const qr = (await printQRAsImage(qr_link, 12));
|
||||
|
||||
// Agregar saltos y corte después del QR
|
||||
const footer = iconv.encode(line(1) + cut(), "CP850");
|
||||
|
||||
// Combinar todo
|
||||
const data = Buffer.concat([textData, qr, footer]);
|
||||
|
||||
// Enviar a la impresora
|
||||
printer.printDirect({
|
||||
data,
|
||||
printer: printer.getDefaultPrinterName(),
|
||||
type: "RAW",
|
||||
success: jobID => console.log("Trabajo enviado:", jobID),
|
||||
error: err => console.error("Error:", err)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error al imprimir:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function ticketFacturadorB(
|
||||
comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
||||
) {
|
||||
|
||||
try {
|
||||
let ticket = "";
|
||||
|
||||
// Encabezado
|
||||
ticket += init();
|
||||
ticket += center();
|
||||
ticket += boldOn() + comercio + boldOff() + line(2);
|
||||
ticket += centerOff();
|
||||
|
||||
ticket += nombre + line();
|
||||
ticket += "CUIT: Nro: " + comercio_cuit + line();
|
||||
ticket += "Ing. Brutos: " + comercio_cuit + line();
|
||||
ticket += "Direccion: " + direccion + line();
|
||||
ticket += `${localidad} (${provincia})` + line();
|
||||
ticket += "Inicio de Actividades: " + inicio_actividades + line();
|
||||
ticket += condicion_iva_comercio + line();
|
||||
ticket += "PUNTO DE VENTA: " + punto_venta + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += "TICKET / FACTURA B" + line();
|
||||
ticket += "Nro: " + comprobante + line();
|
||||
ticket += "Fecha: " + fecha + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += "TIPO CLIENTE: " + cliente_condicion + line();
|
||||
ticket += "CLIENTE: " + cliente + line();
|
||||
ticket += "CUIT: " + cliente_cuit + line();
|
||||
ticket += "DIRECCION: " + cliente_direccion + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += printProducts(JSON.parse(productos)) + line()
|
||||
|
||||
// Total
|
||||
ticket += printBetween("Total: ", `$${total}`) + line(2);
|
||||
|
||||
ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
|
||||
ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
|
||||
ticket += "IVA CONTENIDO: $" + iva_contenido + line()
|
||||
ticket += "CAE: " + cae + line()
|
||||
ticket += "Vencimiento CAE: " + vencimiento_cae + line()
|
||||
|
||||
ticket += line(2) + center()
|
||||
|
||||
// Convertir texto a CP850 ANTES del QR
|
||||
const textData = iconv.encode(ticket, "CP850");
|
||||
|
||||
// Generar QR como Buffer binario
|
||||
const qr = (await printQRAsImage(qr_link, 12));
|
||||
|
||||
// Agregar saltos y corte después del QR
|
||||
const footer = iconv.encode(line(1) + cut(), "CP850");
|
||||
|
||||
// Combinar todo
|
||||
const data = Buffer.concat([textData, qr, footer]);
|
||||
|
||||
// Enviar a la impresora
|
||||
printer.printDirect({
|
||||
data,
|
||||
printer: printer.getDefaultPrinterName(),
|
||||
type: "RAW",
|
||||
success: jobID => console.log("Trabajo enviado:", jobID),
|
||||
error: err => console.error("Error:", err)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error al imprimir:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async function ticketFacturadorC(
|
||||
comercio, comercio_cuit, inicio_actividades, condicion_iva_comercio, punto_venta, cae, vencimiento_cae, iva_contenido, cliente_cuit, cliente_condicion, cliente_direccion, nombre, direccion, cp, localidad, provincia, pais, telefono, mensaje, comprobante, cliente, fecha, qr_link, productos, total
|
||||
) {
|
||||
|
||||
try {
|
||||
let ticket = "";
|
||||
|
||||
// Encabezado
|
||||
ticket += init();
|
||||
ticket += center();
|
||||
ticket += boldOn() + comercio + boldOff() + line(2);
|
||||
ticket += centerOff();
|
||||
|
||||
ticket += nombre + line();
|
||||
ticket += "CUIT: Nro: " + comercio_cuit + line();
|
||||
ticket += "Ing. Brutos: " + comercio_cuit + line();
|
||||
ticket += "Direccion: " + direccion + line();
|
||||
ticket += `${localidad} (${provincia})` + line();
|
||||
ticket += "Inicio de Actividades: " + inicio_actividades + line();
|
||||
ticket += condicion_iva_comercio + line();
|
||||
ticket += "PUNTO DE VENTA: " + punto_venta + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += "TICKET / FACTURA C" + line();
|
||||
ticket += "Nro: " + comprobante + line();
|
||||
ticket += "Fecha: " + fecha + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += "TIPO CLIENTE: " + cliente_condicion + line();
|
||||
ticket += "CLIENTE: " + cliente + line();
|
||||
ticket += "CUIT: " + cliente_cuit + line();
|
||||
ticket += "DIRECCION: " + cliente_direccion + line();
|
||||
|
||||
ticket += printDoubleLine()
|
||||
|
||||
ticket += printProducts(JSON.parse(productos)) + line()
|
||||
|
||||
// Total
|
||||
ticket += printBetween("Total: ", `$${total}`);
|
||||
ticket += line(2);
|
||||
|
||||
ticket += boldOn() + "TRANSPARENCIA FISCAL" + boldOff() + line();
|
||||
ticket += "Reg. Transp. Fiscal (Ley 27743)" + line()
|
||||
ticket += "CAE: " + cae + line()
|
||||
ticket += "Vencimiento CAE: " + vencimiento_cae + line()
|
||||
|
||||
ticket += line(2) + center()
|
||||
|
||||
// Convertir texto a CP850 ANTES del QR
|
||||
const textData = iconv.encode(ticket, "CP850");
|
||||
|
||||
// Generar QR como Buffer binario
|
||||
const qr = (await printQRAsImage(qr_link, 12));
|
||||
|
||||
// Agregar saltos y corte después del QR
|
||||
const footer = iconv.encode(line(1) + cut(), "CP850");
|
||||
|
||||
// Combinar todo
|
||||
const data = Buffer.concat([textData, qr, footer]);
|
||||
|
||||
// Enviar a la impresora
|
||||
printer.printDirect({
|
||||
data,
|
||||
printer: printer.getDefaultPrinterName(),
|
||||
type: "RAW",
|
||||
success: jobID => console.log("Trabajo enviado:", jobID),
|
||||
error: err => console.error("Error:", err)
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error al imprimir:", error);
|
||||
}
|
||||
}
|
||||
|
||||
app.listen(3030, () => {
|
||||
console.log("Servicio de impresion de tickets inicializado en el puerto 3030");
|
||||
// testQROnly();
|
||||
})
|
||||
Reference in New Issue
Block a user