Migrated README.md images to .docs folder. Moved print logic to own module. Implemented ESC/POS printer support. Added logo_grayscale_dark.png. Replaced PDF logo. Implemented size.js util for dynamic PDF page length. Updated README.md. Updated auth output log with type. Updated print output log with printer ip

This commit is contained in:
Glenn de Haan
2024-08-30 20:56:11 +02:00
parent d6b2071a98
commit 3c2155346a
19 changed files with 373 additions and 108 deletions

BIN
.docs/images/desktop_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
.docs/images/desktop_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
.docs/images/desktop_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
.docs/images/desktop_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
.docs/images/mobile_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
.docs/images/mobile_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
.docs/images/mobile_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
.docs/images/mobile_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -4,7 +4,7 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net
[![Image Size](https://img.shields.io/docker/image-size/glenndehaan/unifi-voucher-site)](https://hub.docker.com/r/glenndehaan/unifi-voucher-site)
![Vouchers Overview - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/b0d5c208-2ac7-444e-977d-31287ff19e8b)
![Vouchers Overview - Desktop](.docs/images/desktop_1.png)
> Upgrading from 2.x to 3.x? Please take a look at the [migration guide](#migration-from-2x-to-3x)
@@ -27,6 +27,7 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net
- TailwindCSS
- NodeMailer
- PDFKit
- Node Thermal Printer
## Prerequisites
@@ -94,8 +95,10 @@ services:
SERVICE_WEB: 'true'
# Enable/disable the API
SERVICE_API: 'false'
# Enable/disable the printer and set the preferred type, currently supported types: pdf
# Enable/disable the printer and set the preferred type, currently supported types: pdf, escpos
PRINTER_TYPE: ''
# IP address to your network enabled ESC/POS compatible printer (Only required when using PRINTER_TYPE: 'escpos')
PRINTER_IP: '192.168.1.1'
# SMTP Mail from email address (optional)
SMTP_FROM: ''
# SMTP Mail server hostname/ip (optional)
@@ -273,21 +276,46 @@ To enable the print feature, you need to set the following environment variables
```env
PRINTER_TYPE: ''
PRINTER_IP: ''
```
Heres what each variable represents:
- **`PRINTER_TYPE`**: Sets the printer type used by UniFi Voucher Site. Currently supported options: pdf
- **`PRINTER_TYPE`**: Sets the printer type used by UniFi Voucher Site. Supported options:
- `pdf`: For generating PDF files formatted for 80mm paper width.
- `escpos`: For printing directly to network-enabled ESC/POS compatible printers.
- **`PRINTER_IP`**: Specifies the IP address of the network-enabled ESC/POS printer. This variable is only required when `PRINTER_TYPE` is set to `escpos`.
### Usage
Once your 80mm receipt printer is configured and connected, you can easily print vouchers directly from the UniFi Voucher Site application. Simply navigate to the voucher within the interface and click on the "Print" button.
#### PDF
If you're using the PDF option, once your 80mm receipt printer is configured and connected to your local client, you can easily export vouchers to pdf from the UniFi Voucher Site application. Simply navigate to the voucher within the interface and click on the "Print" button.
The application will automatically format the voucher for 80mm paper width, ensuring optimal printing results. Depending on your printer settings and preferences, you may adjust print quality, paper type, and other printing parameters to suit your needs.
### Example Print PDF
##### Example PDF
![Example Print PDF](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/e86d0789-47d2-4630-a7fe-291a4fa9502f)
![Example PDF](.docs/images/pdf_example.png)
#### ESC/POS
For network-enabled ESC/POS compatible printers, set the `PRINTER_TYPE` to `escpos` and provide the printer's IP address in the `PRINTER_IP` variable. Once configured, you can print vouchers directly to your network printer from the UniFi Voucher Site application.
Just like with PDF printing, navigate to the voucher and click on the "Print" button. The application will send the print job directly to the ESC/POS printer over the network, ensuring quick and seamless voucher printing. Make sure your printer supports ESC/POS commands and is correctly configured to accept print jobs over the network.
##### Tested Printers
- EPSON TM-T88V
- EPSON TM-T20X
- EPSON TM-T82IIIL
- Posman BTP-R880NP
- NetumScan NT-8360 / 80-V
##### Example Print
![Example Print](.docs/images/escpos_example.jpg)
## Email Functionality
@@ -323,7 +351,7 @@ Once the SMTP environment variables are configured, the email feature will be av
### Example Email
![Example Email](https://github.com/user-attachments/assets/45615db3-df76-48b0-ad30-05236e3754c1)
![Example Email](.docs/images/email_example.png)
## OpenID Connect (OIDC) Authentication
@@ -416,28 +444,28 @@ Below is a list of tested Identity Providers (IdPs) with detailed integration in
## Screenshots
### Login (Desktop)
![Login - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/5f89ecbd-7e03-4fd0-ae7d-279d16321384)
![Login - Desktop](.docs/images/desktop_0.png)
### Vouchers Overview (Desktop)
![Vouchers Overview - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/b0d5c208-2ac7-444e-977d-31287ff19e8b)
![Vouchers Overview - Desktop](.docs/images/desktop_1.png)
### Create Voucher (Desktop)
![Create Voucher - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/72f8dcf0-6642-4c89-849f-21cfbcc488ab)
![Create Voucher - Desktop](.docs/images/desktop_2.png)
### Voucher Details (Desktop)
![Voucher Details - Desktop](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/b84ad74c-afaa-4bf1-8bc1-398fb0450ff1)
![Voucher Details - Desktop](.docs/images/desktop_3.png)
### Login (Mobile)
![Login - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/d74bc487-5b80-4bb6-8617-da870cdf4cec)
![Login - Mobile](.docs/images/mobile_0.png)
### Vouchers Overview (Mobile)
![Voucher Overview - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/c986e03d-5edf-4b04-8903-0b42ff1c4fc9)
![Voucher Overview - Mobile](.docs/images/mobile_1.png)
### Create Voucher (Mobile)
![Create Voucher - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/f1cef8c8-a7a5-4238-8a2e-461835375f29)
![Create Voucher - Mobile](.docs/images/mobile_2.png)
### Voucher Details (Mobile)
![Voucher Details - Mobile](https://github.com/glenndehaan/unifi-voucher-site/assets/7496187/28b8f97b-8042-4e6d-b1dc-8386860a1e39)
![Voucher Details - Mobile](.docs/images/mobile_3.png)
## Migration Guide

View File

@@ -23,6 +23,7 @@ services:
SERVICE_WEB: 'true'
SERVICE_API: 'false'
PRINTER_TYPE: ''
PRINTER_IP: '192.168.1.1'
SMTP_FROM: ''
SMTP_HOST: ''
SMTP_PORT: ''

236
modules/print.js Normal file
View File

@@ -0,0 +1,236 @@
/**
* Import base packages
*/
const PDFDocument = require('pdfkit');
const ThermalPrinter = require('node-thermal-printer').printer;
const PrinterTypes = require('node-thermal-printer').types;
/**
* Import own modules
*/
const log = require('./log');
const config = require('./config');
const time = require('../utils/time');
const bytes = require('../utils/bytes');
const size = require('../utils/size');
/**
* Define global variables
*/
const printerIp = config('printer_ip') || process.env.PRINTER_IP || '192.168.1.1';
/**
* Exports the printer module
*/
module.exports = {
/**
* Generates a voucher as a PDF
*
* @param voucher
* @return {Promise<unknown>}
*/
pdf: (voucher) => {
return new Promise((resolve) => {
const doc = new PDFDocument({
bufferPages: true,
size: [226.77165354330398, size(voucher)],
margins : {
top: 20,
bottom: 20,
left: 20,
right: 20
}
});
const buffers = [];
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => {
log.info('[Printer] PDF generation completed!');
resolve(buffers);
});
doc.image('public/images/logo_grayscale_dark.png', 75, 15, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
doc.font('Helvetica-Bold')
.fontSize(20)
.text(`WiFi Voucher Code`, {
align: 'center'
});
doc.font('Helvetica-Bold')
.fontSize(15)
.text(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`, {
align: 'center'
});
doc.moveDown(2);
doc.font('Helvetica-Bold')
.fontSize(12)
.text(`Voucher Details`);
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`--------------------------------------------------------`);
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Type: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(voucher.quota === 0 ? 'Multi-use' : 'Single-use');
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Duration: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(time(voucher.duration));
if(voucher.qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Data Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_usage_quota, 2)}`);
}
if(voucher.qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Download Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_rate_max_down, 1, true)}`);
}
if(voucher.qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Upload Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_rate_max_up, 1, true)}`);
}
doc.end();
});
},
/**
* Sends a print job to an ESC/POS compatible network printer
*
* @param voucher
* @return {Promise<unknown>}
*/
escpos: (voucher) => {
return new Promise(async (resolve, reject) => {
const printer = new ThermalPrinter({
type: PrinterTypes.EPSON,
interface: `tcp://${printerIp}`
});
const status = await printer.isPrinterConnected();
if(!status) {
reject('Unable to connect to printer!');
return;
}
printer.setTypeFontB();
printer.alignCenter();
printer.newLine();
await printer.printImage(`${process.cwd()}/public/images/logo_grayscale_dark.png`);
printer.newLine();
printer.alignCenter();
printer.newLine();
printer.setTextSize(2, 2);
printer.println('WiFi Voucher Code');
printer.setTextSize(1, 1);
printer.println(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`);
printer.setTextNormal();
printer.newLine();
printer.newLine();
printer.newLine();
printer.newLine();
printer.newLine();
printer.alignLeft();
printer.setTypeFontB();
printer.setTextSize(1, 1);
printer.println('Voucher Details');
printer.setTextNormal();
printer.drawLine();
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Type:');
printer.invert(false);
printer.print(voucher.quota === 0 ? ' Multi-use' : ' Single-use');
printer.newLine();
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Duration:');
printer.invert(false);
printer.print(` ${time(voucher.duration)}`);
printer.newLine();
if(voucher.qos_usage_quota) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Data Limit:');
printer.invert(false);
printer.print(` ${bytes(voucher.qos_usage_quota, 2)}`);
printer.newLine();
}
if(voucher.qos_rate_max_down) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Download Limit:');
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_down, 1, true)}`);
printer.newLine();
}
if(voucher.qos_rate_max_up) {
printer.setTextDoubleHeight();
printer.invert(true);
printer.print('Upload Limit:');
printer.invert(false);
printer.print(` ${bytes(voucher.qos_rate_max_up, 1, true)}`);
printer.newLine();
}
printer.newLine();
printer.newLine();
printer.newLine();
printer.newLine();
printer.cut();
printer.beep(2, 2);
try {
await printer.execute();
log.info('[Printer] Data send to printer!');
resolve(true);
} catch (error) {
reject(error);
}
});
}
};

58
package-lock.json generated
View File

@@ -16,6 +16,7 @@
"js-logger": "^1.6.1",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"node-thermal-printer": "^4.4.3",
"node-unifi": "^2.5.1",
"nodemailer": "^6.9.14",
"pdfkit": "^0.15.0"
@@ -931,6 +932,12 @@
"node": ">=4"
}
},
"node_modules/dank-do-while": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dank-do-while/-/dank-do-while-0.1.2.tgz",
"integrity": "sha512-pyMrgXfugjvdernLCcXNixYqGijUZzYxrb+hAeED2EBpNbctCCxdA/O6NIQXn6vIjwzY1IMmV29d2Y6vaayrwQ==",
"license": "MIT"
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -2555,6 +2562,30 @@
"node": ">= 0.6"
}
},
"node_modules/node-thermal-printer": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/node-thermal-printer/-/node-thermal-printer-4.4.3.tgz",
"integrity": "sha512-avpfLcNRxn+rhhDcqho1Q+0SUZo7ZCq3IGXAqXeVf0NBEZJUV7bzjIqU/qDCl+LgOBZIQtsmEg8oCFxzA+ojDA==",
"license": "ISC",
"dependencies": {
"iconv-lite": "0.5.0",
"pngjs": "3.3.3",
"unorm": "1.4.1",
"write-file-queue": "0.0.1"
}
},
"node_modules/node-thermal-printer/node_modules/iconv-lite": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz",
"integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/node-unifi": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/node-unifi/-/node-unifi-2.5.1.tgz",
@@ -2926,6 +2957,15 @@
"resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz",
"integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g=="
},
"node_modules/pngjs": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.3.3.tgz",
"integrity": "sha512-1n3Z4p3IOxArEs1VRXnZ/RXdfEniAUS9jb68g58FIXMNkPJeZd+Qh4Uq7/e0LVxAQGos1eIUrqrt4FpjdnEd+Q==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
@@ -3822,6 +3862,15 @@
"node": ">= 4.0.0"
}
},
"node_modules/unorm": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/unorm/-/unorm-1.4.1.tgz",
"integrity": "sha512-ZKduSr7qaZXq/R784+M3vudsguIahTX9aVtlfJdMaQj/yQw7A8LhkaS76/a6d0TzeBtiMDl+clvrLxq6HG/nAA==",
"license": "MIT or GPL-2.0",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -3994,6 +4043,15 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/write-file-queue": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/write-file-queue/-/write-file-queue-0.0.1.tgz",
"integrity": "sha512-uUInKYzJjhvZ5UJA27tsqbw6s9EEpwe6r4mdpN95t2E4aKzJEJbZg2EvIleTGaja+xtZYx376Jd2WWu5LEQP0w==",
"license": "MIT",
"dependencies": {
"dank-do-while": "^0.1.2"
}
},
"node_modules/ws": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",

View File

@@ -27,6 +27,7 @@
"js-logger": "^1.6.1",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"node-thermal-printer": "^4.4.3",
"node-unifi": "^2.5.1",
"nodemailer": "^6.9.14",
"pdfkit": "^0.15.0"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

107
server.js
View File

@@ -6,7 +6,6 @@ const os = require('os');
const express = require('express');
const multer = require('multer');
const cookieParser = require('cookie-parser');
const PDFDocument = require('pdfkit');
/**
* Import own modules
@@ -20,6 +19,7 @@ const types = require('./utils/types');
const time = require('./utils/time');
const bytes = require('./utils/bytes');
const unifi = require('./modules/unifi');
const print = require('./modules/print');
const mail = require('./modules/mail');
const oidc = require('./modules/oidc');
@@ -48,6 +48,7 @@ const webService = process.env.SERVICE_WEB ? process.env.SERVICE_WEB !== 'false'
const apiService = config('service_api') || (process.env.SERVICE_API === 'true') || false;
const authDisabled = (process.env.AUTH_DISABLE === 'true') || false;
const printerType = config('printer_type') || process.env.PRINTER_TYPE || '';
const printerIp = config('printer_ip') || process.env.PRINTER_IP || '192.168.1.1';
const smtpFrom = config('smtp_from') || process.env.SMTP_FROM || '';
const smtpHost = config('smtp_host') || process.env.SMTP_HOST || '';
const smtpPort = config('smtp_port') || process.env.SMTP_PORT || 25;
@@ -97,12 +98,12 @@ log.info(`[Voucher][Custom] ${voucherCustom ? 'Enabled!' : 'Disabled!'}`);
/**
* Log auth status
*/
log.info(`[Auth] ${authDisabled ? 'Disabled!' : 'Enabled!'}`);
log.info(`[Auth] ${authDisabled ? 'Disabled!' : `Enabled! Type: ${(oidcIssuerBaseUrl !== '' && oidcAppBaseUrl !== '' && oidcClientId !== '') ? 'OIDC' : 'Internal'}`}`);
/**
* Log printer status
*/
log.info(`[Printer] ${printerType !== '' ? `Enabled! Type: ${printerType}` : 'Disabled!'}`);
log.info(`[Printer] ${printerType !== '' ? `Enabled! Type: ${printerType}${printerType === 'escpos' ? `, IP: ${printerIp}` : ''}` : 'Disabled!'}`);
/**
* Log email status
@@ -297,105 +298,25 @@ if(webService) {
});
if(voucher) {
const doc = new PDFDocument({
bufferPages: true,
size: [226.77165354330398, 290],
margins : {
top: 20,
bottom: 20,
left: 20,
right: 20
}
});
const buffers = [];
doc.on('data', buffers.push.bind(buffers));
doc.on('end', () => {
let pdfData = Buffer.concat(buffers);
if(printerType === 'pdf') {
const buffers = await print.pdf(voucher);
const pdfData = Buffer.concat(buffers);
res.writeHead(200, {
'Content-Length': Buffer.byteLength(pdfData),
'Content-Type': 'application/pdf',
'Content-Disposition': `attachment;filename=voucher_${req.params.id}.pdf`
}).end(pdfData);
});
doc.image('public/images/logo_grayscale.png', 75, 15, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
doc.font('Helvetica-Bold')
.fontSize(20)
.text(`WiFi Voucher Code`, {
align: 'center'
});
doc.font('Helvetica-Bold')
.fontSize(15)
.text(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`, {
align: 'center'
});
doc.moveDown(2);
doc.font('Helvetica-Bold')
.fontSize(12)
.text(`Voucher Details`);
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`--------------------------------------------------------`);
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Type: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(voucher.quota === 0 ? 'Multi-use' : 'Single-use');
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Duration: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(time(voucher.duration));
if(voucher.qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Data Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_usage_quota, 2)}`);
}
if(voucher.qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Download Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_rate_max_down, 1, true)}`);
}
if(printerType === 'escpos') {
const printResult = await print.escpos(voucher).catch((e) => {
res.cookie('flashMessage', JSON.stringify({type: 'error', message: e}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
});
if(voucher.qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`Upload Limit: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_rate_max_up, 1, true)}`);
if(printResult) {
res.cookie('flashMessage', JSON.stringify({type: 'info', message: `Voucher send to printer!`}), {httpOnly: true, expires: new Date(Date.now() + 24 * 60 * 60 * 1000)}).redirect(302, `${req.headers['x-ingress-path'] ? req.headers['x-ingress-path'] : ''}/vouchers`);
}
}
doc.end();
} else {
res.status(404);
res.render('404', {

20
utils/size.js Normal file
View File

@@ -0,0 +1,20 @@
/**
* Util function to calculate paper size based on voucher data
*/
module.exports = (voucher) => {
let base = 260;
if(voucher.qos_usage_quota) {
base += 10;
}
if(voucher.qos_rate_max_down) {
base += 10;
}
if(voucher.qos_rate_max_up) {
base += 10;
}
return base;
}