A wrapping client and label printing

For now, the server does the printing.
Later it should probably be split into a different service.
This commit is contained in:
Paul Mathieu 2022-09-07 19:08:49 +02:00
parent 0c9c893237
commit 235d9272ed
5 changed files with 203 additions and 13 deletions

45
client/wrap.html Normal file
View File

@ -0,0 +1,45 @@
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>chikin wrap 0.1</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Compiled and minified JavaScript -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="wrap.js"></script>
<style>
body {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex: 1 0 auto;
}
</style>
</head>
<body>
<header class="page-header center amber brown-text">
<h1>Chikin wrap</h1>
</header>
<main class="container row">
<div class="col m6 offset-m3 s12">
<div id="appbody"></div>
</div>
</main>
<footer class="page-footer">
<div class="container row">
<span class="col right">displayed with recycled electrons.</span>
</div>
</footer>
</body>
</html>

102
client/wrap.js Normal file
View File

@ -0,0 +1,102 @@
const config = 'dev';
const backend_api = {
'prod': '/zetikettes/srv/',
'dev': 'http://scrawny.local:3000',
}[config];
const token = localStorage.getItem('token');
async function getBandz() {
const uri = `${backend_api}/bandz`;
const res = await $.ajax(uri, {headers: {Authorization: `Bearer ${token}`}});
return res.bandz;
}
async function wrapAChikin(req) {
const uri = `${backend_api}/wrap`;
const res = await $.ajax(uri, {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(req),
headers: {Authorization: `Bearer ${token}`},
});
return res.chikinId;
}
function printLabel(chikinId) {
const uri = `${backend_api}/print`;
$.ajax(uri, {
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({chikinId}),
headers: {Authorization: `Bearer ${token}`},
});
}
$(document).ready(() => {
const appbody = $("#appbody");
const bandSelect = $('<div class="input-field col s12">')
.append(
$('<select>')
.append('<option disabled selected>Choose a band</option>')
)
.append('<label>Band</label>');
const block = $('<div class="section">');
block
.append(bandSelect)
.append($('<div class="input-field col s12">')
.append('<input type="text" class="datepicker">')
.append('<label>Date of wrapping</label>')
);
const loader = $('<div class="progress"><div class="indeterminate"></div></div>')
.hide();
const action = $('<div class="section">')
.append(
$('<a class="btn">wrap-a-chikin<i class="material-icons right">send</i></a>')
.click(async () => {
loader.show();
$('.btn').addClass("disabled");
const req = {
bandId: $('select').val(),
wrapDate: $('.datepicker').val(),
weight: $('#weight').val(),
};
try {
const chikinId = await wrapAChikin(req);
printLabel(chikinId);
} finally {
loader.hide();
$('#weight').val('');
$('.btn').removeClass('disabled');
}
})
);
block
.append(
$('<div class="input-field col s12">')
.append('<input type="text" inputmode="decimal" id="weight">')
.append('<label>weight (g)</label>')
)
.append(
$('<div class="input-field col s12">')
.append(action)
.append(loader)
)
;
appbody.append(block);
getBandz().then(bandz => {
$('select').append(bandz.map(band => `<option value="${band.id}">${band.name}</option>`));
$('select').formSelect();
});
$('select').formSelect();
$('.datepicker').datepicker({setDefaultDate: true, defaultDate: new Date()});
});

View File

@ -1,4 +1,6 @@
const bodyParser = require('body-parser');
const child_process = require('child_process');
const cors = require('cors');
const express = require('express');
require('express-async-errors');
const jwt = require('jsonwebtoken');
@ -28,9 +30,9 @@ CREATE TABLE IF NOT EXISTS Chikinz (
id INTEGER PRIMARY KEY AUTOINCREMENT,
bandId INTEGER NOT NULL,
weight REAL NOT NULL,
killedDate DATE,
wrappedDate DATE NOT NULL,
soldDate DATE,
killDate DATE,
wrapDate DATE NOT NULL,
sellDate DATE,
misc TEXT
);`);
@ -38,17 +40,15 @@ makeTable(`
CREATE TABLE IF NOT EXISTS Bandz (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
receivedDate DATE,
receiveDate DATE,
misc TEXT
);`);
const app = express();
function addChikin(db, chikin) {
const sql_insert = 'INSERT INTO Chikinz (bandId, weight, killedDate, wrappedDate) VALUES (?, ?, ?, ?);';
const {bandId, weight, killedDate, wrappedDate} = chikin;
const sql_insert = 'INSERT INTO Chikinz (bandId, weight, killDate, wrapDate) VALUES (?, ?, ?, ?);';
const {bandId, weight, killDate, wrapDate} = chikin;
return new Promise((resolve, reject) => {
db.run(sql_insert, [bandId, weight, killedDate, wrappedDate], function(err) {
db.run(sql_insert, [bandId, weight, killDate, wrapDate], function(err) {
if (err) {
return reject(err);
}
@ -74,6 +74,17 @@ function getBandz(db) {
});
}
function getChikin(db, chikinId) {
return new Promise((resolve, reject) => {
db.get('SELECT * from Chikinz where id=?', chikinId, (err, row) => {
if (err) {
return reject(err);
}
resolve(row);
});
});
}
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
@ -98,7 +109,10 @@ const authenticateJWT = (req, res, next) => {
}
};
const app = express();
app.use(bodyParser.json());
app.use(cors());
app.listen(3000, () => {
console.log('Server started (http://localhost:3000/)!');
@ -118,7 +132,6 @@ app.get('/bandz', authenticateJWT, async (req, res) => {
app.post('/toktok', (req, res) => {
const {password} = req.body;
console.log(req.body);
if (password != 'goldchocoboisbestchocobo.goldchocoboisonlychocobo') {
return res.json({message: 'This is not the way.'});
@ -134,9 +147,30 @@ app.post('/wrap', authenticateJWT, async (req, res) => {
const chikin = req.body;
console.log(chikin);
const {lastID} = await addChikin(db, chikin);
res.json({message: 'This is the way.', id: lastID});
res.json({message: 'This is the way.', chikinId: lastID});
});
app.post('/print', authenticateJWT, async (req, res) => {
const {chikinId} = req.body;
const qr_url = `http://lafermedumalpas.fr/chikinz/${chikinId}`;
const chikin = await getChikin(db, chikinId);
const line0 = 'Poulet fermier bio';
const line1 = `${chikin.weight.toFixed(2)} kg`;
const pricePerKg = 12;
const price = chikin.weight * pricePerKg;
const line2 = `${pricePerKg.toFixed(2)} €/kg - ${price.toFixed(2)}`;
const args = ['/Users/paul/scratch/printer/catprint.py',
'--template0', [qr_url, line0, line1, line2].join(';')];
const proc = child_process.spawn('python', args);
proc.stdout.on('data', data => console.log(`stdout: ${data}`));
proc.stderr.on('data', data => console.log(`stderr: ${data}`));
res.json({message: 'This is the way.'});
});
// catch errors

View File

@ -6,6 +6,7 @@
"author": "Paul Mathieu <paul@ponteilla.net>",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.18.1",
"express-async-errors": "^3.1.1",
"jsonwebtoken": "^8.5.1",

View File

@ -266,6 +266,14 @@ cookie@0.5.0:
resolved "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz"
integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
@ -980,7 +988,7 @@ npmlog@^6.0.0:
gauge "^4.0.3"
set-blocking "^2.0.0"
object-assign@^4.1.1:
object-assign@^4, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@ -1352,7 +1360,7 @@ utils-merge@1.0.1:
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
vary@~1.1.2:
vary@^1, vary@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==