Skip to content

Commit

Permalink
Implemented expired states within templates. Implemented expired filt…
Browse files Browse the repository at this point in the history
…er. Implemented notes within voucher creation. Implemented Notes within voucher overview and detail pages. Implemented notes sort option. Fixed incorrect voucher type when custom quotas are in use. Implemented quota display within templates. Implemented bulk printing for both PDF and ESC/POS modules. Updated README.md
  • Loading branch information
glenndehaan committed Jan 9, 2025
1 parent 4691a90 commit 9feeaa6
Show file tree
Hide file tree
Showing 8 changed files with 415 additions and 123 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net
- **Web and API Services**: Access the service via a web interface or integrate with other systems using a REST API.
- **Docker Support**: Easily deploy using Docker, with customizable environment settings.
- **Home Assistant Add-on**: Seamlessly integrate with Home Assistant for centralized management.
- **Receipt Printing**: Supports printing vouchers with 80mm thermal printers.
- **Receipt Printing**: Supports printing vouchers with 80mm thermal printers. Via compatible PDFs or ESC/POS enabled network printers.
- **Bulk Printing**: Export/print multiple Vouchers in one go.
- **Email Functionality**: Automatically send vouchers via SMTP.
- **Localized Email/Print Templates** Fully localized templates, with support for multiple languages.
- **Scan to Connect QR Codes** Quickly connect users via a phone's camera. (Available within Email and Print Layouts)
Expand Down
209 changes: 123 additions & 86 deletions modules/print.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,27 @@ module.exports = {
/**
* Generates a voucher as a PDF
*
* @param voucher
* @param content
* @param language
* @param multiPage
* @return {Promise<unknown>}
*/
pdf: (voucher, language) => {
pdf: (content, language, multiPage= false) => {
return new Promise(async (resolve) => {
// Create new translator
const t = translation('print', language);

// Set vouchers based on multiPage parameter
let vouchers = [];
if(multiPage) {
vouchers = [...content];
} else {
vouchers = [content];
}

const doc = new PDFDocument({
bufferPages: true,
size: [226.77165354330398, size(voucher)],
size: [226.77165354330398, size(vouchers[0])],
margins : {
top: 20,
bottom: 20,
Expand All @@ -54,124 +63,148 @@ module.exports = {
resolve(buffers);
});

doc.image('public/images/logo_grayscale_dark.png', 75, 15, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
for(let item = 0; item < vouchers.length; item++) {
if(item > 0) {
doc.addPage({
size: [226.77165354330398, size(vouchers[item])],
margins : {
top: 20,
bottom: 20,
left: 20,
right: 20
}
});

doc.font('Helvetica-Bold')
.fontSize(20)
.text(`${t('title')}`, {
align: 'center'
});
doc.font('Helvetica-Bold')
.fontSize(15)
.text(`${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`, {
align: 'center'
});
doc.moveDown(1);
}

doc.moveDown(2);
doc.image('public/images/logo_grayscale_dark.png', 75, 15, {
fit: [75, 75],
align: 'center',
valign: 'center'
});
doc.moveDown(6);

if(variables.unifiSsid !== '') {
doc.font('Helvetica')
.fontSize(10)
.text(`${t('connect')}: `, {
continued: true
doc.font('Helvetica-Bold')
.fontSize(20)
.text(`${t('title')}`, {
align: 'center'
});
doc.font('Helvetica-Bold')
.fontSize(10)
.text(variables.unifiSsid, {
continued: true
.fontSize(15)
.text(`${vouchers[item].code.slice(0, 5)}-${vouchers[item].code.slice(5)}`, {
align: 'center'
});

if(variables.unifiSsidPassword !== '') {
doc.font('Helvetica')
.fontSize(10)
.text(`,`);
doc.moveDown(2);

if (variables.unifiSsid !== '') {
doc.font('Helvetica')
.fontSize(10)
.text(`${t('password')}: `, {
.text(`${t('connect')}: `, {
continued: true
});
doc.font('Helvetica-Bold')
.fontSize(10)
.text(variables.unifiSsidPassword, {
.text(variables.unifiSsid, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(` ${t('or')},`);
} else {
doc.font('Helvetica')
.fontSize(10)
.text(` ${t('or')},`);
}

doc.font('Helvetica')
.fontSize(10)
.text(`${t('scan')}:`);

doc.image(await qr(), 75, variables.unifiSsidPassword !== '' ? 215 : 205, {fit: [75, 75], align: 'center', valign: 'center'});
doc.moveDown(6);
if (variables.unifiSsidPassword !== '') {
doc.font('Helvetica')
.fontSize(10)
.text(`,`);
doc.font('Helvetica')
.fontSize(10)
.text(`${t('password')}: `, {
continued: true
});
doc.font('Helvetica-Bold')
.fontSize(10)
.text(variables.unifiSsidPassword, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(` ${t('or')},`);
} else {
doc.font('Helvetica')
.fontSize(10)
.text(` ${t('or')},`);
}

doc.moveDown(2);
}
doc.font('Helvetica')
.fontSize(10)
.text(`${t('scan')}:`);

doc.font('Helvetica-Bold')
.fontSize(12)
.text(`${t('details')}`);
doc.image(await qr(), 75, variables.unifiSsidPassword !== '' ? 215 : 205, {
fit: [75, 75],
align: 'center',
valign: 'center'
});
doc.moveDown(6);

doc.font('Helvetica-Bold')
.fontSize(10)
.text(`--------------------------------------------------------`);
doc.moveDown(2);
}

doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('type')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(voucher.quota === 0 ? t('multiUse') : t('singleUse'));

doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('duration')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(time(voucher.duration));
doc.font('Helvetica-Bold')
.fontSize(12)
.text(`${t('details')}`);

if(voucher.qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('dataLimit')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_usage_quota, 2)}`);
}
.text(`--------------------------------------------------------`);

if(voucher.qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('downloadLimit')}: `, {
.text(`${t('type')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_rate_max_down, 1, true)}`);
}
.text(vouchers[item].quota === 1 ? t('singleUse') : vouchers[item].quota === 0 ? t('multiUse') : t('multiUse'));

if(voucher.qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('uploadLimit')}: `, {
.text(`${t('duration')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(voucher.qos_rate_max_up, 1, true)}`);
.text(time(vouchers[item].duration));

if (vouchers[item].qos_usage_quota) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('dataLimit')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(vouchers[item].qos_usage_quota, 2)}`);
}

if (vouchers[item].qos_rate_max_down) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('downloadLimit')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(vouchers[item].qos_rate_max_down, 1, true)}`);
}

if (vouchers[item].qos_rate_max_up) {
doc.font('Helvetica-Bold')
.fontSize(10)
.text(`${t('uploadLimit')}: `, {
continued: true
});
doc.font('Helvetica')
.fontSize(10)
.text(`${bytes(vouchers[item].qos_rate_max_up, 1, true)}`);
}
}

doc.end();
Expand Down Expand Up @@ -260,7 +293,7 @@ module.exports = {
printer.invert(true);
printer.print(`${t('type')}:`);
printer.invert(false);
printer.print(voucher.quota === 0 ? ` ${t('multiUse')}` : ` ${t('singleUse')}`);
printer.print(voucher.quota === 1 ? ` ${t('singleUse')}` : voucher.quota === 0 ? ` ${t('multiUse')}` : ` ${t('multiUse')}`);
printer.newLine();

printer.setTextDoubleHeight();
Expand Down Expand Up @@ -307,7 +340,11 @@ module.exports = {
try {
await printer.execute();
log.info('[Printer] Data send to printer!');
resolve(true);

// Ensure cheap printers have cleared the buffer before allowing new actions
setTimeout(() => {
resolve(true);
}, 1500);
} catch (error) {
reject(error);
}
Expand Down
9 changes: 5 additions & 4 deletions modules/unifi.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,22 @@ const startSession = () => {
/**
* UniFi module functions
*
* @type {{create: (function(*, number=, boolean=): Promise<*>), list: (function(boolean=): Promise<*>), remove: (function(*, boolean=): Promise<*>)}}
* @type {{create: (function(*, number=, null=, boolean=): Promise<*>), remove: (function(*, boolean=): Promise<*>), list: (function(boolean=): Promise<*>), guests: (function(boolean=): Promise<*>)}}
*/
const unifiModule = {
/**
* Creates a new UniFi Voucher
*
* @param type
* @param amount
* @param note
* @param retry
* @return {Promise<unknown>}
*/
create: (type, amount = 1, retry = true) => {
create: (type, amount = 1, note = null, retry = true) => {
return new Promise((resolve, reject) => {
startSession().then(() => {
controller.createVouchers(type.expiration, amount, parseInt(type.usage) === 1 ? 1 : 0, null, typeof type.upload !== "undefined" ? type.upload : null, typeof type.download !== "undefined" ? type.download : null, typeof type.megabytes !== "undefined" ? type.megabytes : null).then((voucher_data) => {
controller.createVouchers(type.expiration, amount, parseInt(type.usage) === 1 ? 1 : 0, note, typeof type.upload !== "undefined" ? type.upload : null, typeof type.download !== "undefined" ? type.download : null, typeof type.megabytes !== "undefined" ? type.megabytes : null).then((voucher_data) => {
if(amount > 1) {
log.info(`[UniFi] Created ${amount} vouchers`);
resolve(true);
Expand All @@ -107,7 +108,7 @@ const unifiModule = {
log.info('[UniFi] Attempting re-authentication & retry...');

controller = null;
unifiModule.create(type, amount, false).then((e) => {
unifiModule.create(type, amount, note, false).then((e) => {
resolve(e);
}).catch((e) => {
reject(e);
Expand Down
Loading

0 comments on commit 9feeaa6

Please sign in to comment.