Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
7a2607e
Fix gedeelde receipt-state per sessie
meneerhenk May 26, 2026
25080f5
Fix prompt logging voor Python 3 strings
meneerhenk May 26, 2026
033d1c6
Voorkom alias-mutatie bij wegschrijven producten
meneerhenk May 26, 2026
5b383a9
Reset state bij opnieuw inlezen datafiles
meneerhenk May 26, 2026
912dfd8
Fix cash-bonnen zonder account lookup
meneerhenk May 26, 2026
5dc778e
Fix MQTT topic guard voor korte inputtopics
meneerhenk May 26, 2026
f69dfff
Isoleer Session state per instantie
meneerhenk May 26, 2026
f6e68f6
Fix market beheercommando's en bestandsformaat
meneerhenk May 26, 2026
95a4497
Voorkom samenvoegen van verschillende receipt-producten
meneerhenk May 26, 2026
8cdf520
Bewaar undo-transacties als snapshot
meneerhenk May 26, 2026
a585725
Serialiseer background git commits
meneerhenk May 26, 2026
8730466
Maak account en stock writes atomair
meneerhenk May 26, 2026
bc68d64
Vervang pickle writes door JSON met legacy load
meneerhenk May 26, 2026
bf27756
Maak product en market writes atomair
meneerhenk May 26, 2026
4b054de
Isoleer pfand state bij laden
meneerhenk May 26, 2026
b2a4222
Isoleer POS state per instantie
meneerhenk May 26, 2026
50e8270
Isoleer transactieflow state per instantie
meneerhenk May 26, 2026
3d9bbd1
Maak account laden toleranter
meneerhenk May 26, 2026
547dcf6
Maak stock laden toleranter
meneerhenk May 26, 2026
6248078
Maak product market en pfand laden toleranter
meneerhenk May 26, 2026
010c493
Wis stale POS en undo state bij mislukte load
meneerhenk May 26, 2026
8f3478f
Maak members laden toleranter
meneerhenk May 26, 2026
e25cdac
Sla ongeldige numerieke loadregels over
meneerhenk May 26, 2026
ab1fe93
Blokkeer productaliases die met commando's botsen
meneerhenk May 26, 2026
b5dd277
Verplaats hardcoded hosts naar config
meneerhenk May 26, 2026
209070f
Onderdruk third-party deprecation warnings
meneerhenk May 26, 2026
acdff57
Vervang serial dependency door pyserial
meneerhenk May 26, 2026
1341292
Centraliseer validatie van gereserveerde input
meneerhenk May 26, 2026
77f6c4c
Valideer PHP input voor MQTT endpoints
meneerhenk May 26, 2026
1f4ffb7
Log centrale kassa exceptions expliciet
meneerhenk May 26, 2026
78b99dc
Maak transactiebedrag parsing expliciet
meneerhenk May 26, 2026
d718cb8
Maak POS undo en datafile exceptions expliciet
meneerhenk May 26, 2026
8065f6f
Maak product markt en voorraad parsing expliciet
meneerhenk May 26, 2026
50443ec
Maak resterende plugin exceptions expliciet
meneerhenk May 26, 2026
f0ca43e
Centraliseer product input validatie
meneerhenk May 26, 2026
b6441db
Test checkout undo en restore integratie
meneerhenk May 26, 2026
a819a67
Structureer runtime logging
meneerhenk May 26, 2026
2c08db9
Verwijder test debug output
meneerhenk May 26, 2026
c4c572c
Test checkout met accounts stock en POS
meneerhenk May 26, 2026
f95fa46
Open kassalade een keer bij cash checkout
meneerhenk May 26, 2026
34d9049
Maak checkout hooks minder volgordegevoelig
meneerhenk May 26, 2026
fca6feb
Moderniseer spaceconsole command javascript
meneerhenk May 26, 2026
113ee57
Maak kassa frontend javascript strikter
meneerhenk May 26, 2026
712ce3a
Ruim kassa frontend click handlers op
meneerhenk May 26, 2026
9d4d17d
Upgrade jQuery en verwijder ongebruikte UI
meneerhenk May 26, 2026
c51b153
Vervang jquery loops in kassa frontend
meneerhenk May 27, 2026
c94e452
Gebruik native DOM state helpers
meneerhenk May 28, 2026
8d9b015
Splits kassa frontend initialisatie op
meneerhenk May 28, 2026
324b9ab
Groepeer kassa frontend event handlers
meneerhenk May 28, 2026
6e8c2ca
Splits kassa frontend handlers op
meneerhenk May 28, 2026
2e7fea6
Bereik volledige python testdekking
meneerhenk May 28, 2026
d6f7268
fixed layout
meneerhenk May 28, 2026
2383c60
layout fix
meneerhenk May 28, 2026
79a1251
Voeg javascriptlint en checktarget toe
meneerhenk May 28, 2026
14ff0c8
Maak pythonlint groen
meneerhenk May 28, 2026
417bfeb
Laat workflow make check draaien
meneerhenk May 28, 2026
6a8362e
Maak atomic writes CI-bestendig
meneerhenk May 28, 2026
4eace8e
Toon stickerprinter fouten duidelijk
meneerhenk May 28, 2026
e9100a1
Voeg members beheer toe
meneerhenk May 28, 2026
316f302
Splits kassa JavaScript op
meneerhenk May 28, 2026
8819c31
Voeg frontend tests toe
meneerhenk May 28, 2026
8760405
Maak DOM helpers onafhankelijk van jQuery
meneerhenk May 28, 2026
7e15d5e
Verwijder ongebruikte frontend vendors
meneerhenk May 28, 2026
8140956
Splits kassa button transforms uit
meneerhenk May 28, 2026
8154c92
Verminder jQuery gebruik in kassa app
meneerhenk May 28, 2026
3fc104a
Maak spaceconsole onafhankelijk van jQuery
meneerhenk May 28, 2026
184bb03
Test kassa app frontend flow
meneerhenk May 28, 2026
f79fb35
Maak kassa app DOM rendering native
meneerhenk May 28, 2026
f982e56
Verwijder jQuery uit hoofdfrontend
meneerhenk May 28, 2026
171412f
Test frontend startup in browser
meneerhenk May 28, 2026
6498af2
Voeg PHP checks toe
meneerhenk May 28, 2026
9837f95
Test frontend browser interactie
meneerhenk May 28, 2026
a869ce1
Splits frontend stream logica uit
meneerhenk May 28, 2026
15c2f7a
Laat PHP config yaml gebruiken
meneerhenk May 28, 2026
13f38d5
Scherp CI checks aan
meneerhenk May 28, 2026
08ca460
Fix browser smoke test in CI
meneerhenk May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: "eslint:recommended",
parserOptions: {
ecmaVersion: "latest",
sourceType: "script",
},
globals: {
$: "readonly",
dolog: "readonly",
EventSource: "readonly",
jQuery: "readonly",
runtext: "readonly",
showusers: "readonly",
},
overrides: [
{
files: ["tests/js/*.test.js"],
env: {
node: true,
},
},
],
};
27 changes: 24 additions & 3 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,35 @@ jobs:
requirements.txt
requirements-dev.txt

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: package-lock.json

- name: Set up PHP
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
coverage: none

- name: Show tool versions
run: |
python --version
node --version
npm --version
php --version
google-chrome --version || chromium --version || chromium-browser --version || true

- name: Install dependencies
run: |
python -m pip install --upgrade pip
make venv
make pip-sync-dev

- name: Run lint and formatting checks
run: make lint

- name: Run tests
run: make test

- name: Run lint checks
run: make lint
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
config.yaml
.coverage
.pytest_cache/
.venv/
__pycache__/
node_modules/
tests/__pycache__/
tests/plugins/__pycache__/
36 changes: 32 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
PYTHON ?= python3
NPM ?= npm
PHP ?= php
PY_FILES := kassa.py $(shell find plugins tests -type f -name '*.py' | sort)
PHP_FILES := $(shell find www -type f -name '*.php' | sort)

test:
.PHONY: test python-test js-test php-test venv pip-compile pip-sync pip-sync-dev lint python-lint js-lint php-lint check-types fix check

test: python-test js-test php-test

python-test: venv
@. .venv/bin/activate && ${env} ${PYTHON} -m pytest -vvv --cov=. --cov-report term-missing

js-test: node_modules/.package-lock.json
@${NPM} run test:js

php-test:
@${PHP} tests/php/test_php_files.php

check: test lint ## Run tests and linters

venv: .venv/make_venv_complete ## Create virtual environment
.venv/make_venv_complete:
${PYTHON} -m venv .venv
Expand All @@ -22,12 +39,23 @@ pip-sync-dev: ## synchronizes the .venv with the state of requirements.txt
. .venv/bin/activate && ${env} pip install -U pip-tools
. .venv/bin/activate && ${env} ${PYTHON} -m piptools sync requirements.txt requirements-dev.txt

lint: venv ## Do basic linting
lint: python-lint js-lint php-lint ## Do basic linting

python-lint: venv
@. .venv/bin/activate && ${env} ${PYTHON} -m pylint --persistent=no kassa.py plugins
@. .venv/bin/activate && ${env} ${PYTHON} -m black --check kassa.py plugins tests
@. .venv/bin/activate; for file in ${PY_FILES}; do ${env} ${PYTHON} -m black --check --quiet --workers 1 "$$file" || exit $$?; done

node_modules/.package-lock.json: package.json package-lock.json
${NPM} ci

js-lint: node_modules/.package-lock.json
@${NPM} run lint:js

php-lint:
@for file in ${PHP_FILES}; do ${PHP} -l "$$file" || exit $$?; done

check-types: venv ## Check for type issues with mypy
@. .venv/bin/activate && ${env} ${PYTHON} -m mypy --check .

fix:
@. .venv/bin/activate && ${env} ${PYTHON} -m black kassa.py plugins tests
@. .venv/bin/activate; for file in ${PY_FILES}; do ${env} ${PYTHON} -m black --quiet --workers 1 "$$file" || exit $$?; done
71 changes: 71 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import copy
import os

import yaml


CONFIG_PATH = "config.yaml"

DEFAULT_CONFIG = {
"mqtt": {
"host": "localhost",
"port": 1883,
"keepalive": 60,
},
"door": {
"mqtt": {
"host": "mqtt.space.hack42.nl",
"port": 1883,
"keepalive": 60,
"topic": "hack42/brandhok/deuropen",
},
},
"stickers": {
"printer": {
"model": "QL-710W",
"host": "localhost",
"port": 9100,
},
},
"pos": {
"serial": {
"port": "/dev/ttyUSB0",
"baudrate": 19200,
},
},
"logging": {
"level": "INFO",
},
}


def _merge_config(base, override):
merged = copy.deepcopy(base)
for key, value in override.items():
if isinstance(value, dict) and isinstance(merged.get(key), dict):
merged[key] = _merge_config(merged[key], value)
else:
merged[key] = value
return merged


def load_config(path=None):
path = path or os.environ.get("KASSA_CONFIG", CONFIG_PATH)
if not os.path.exists(path):
return copy.deepcopy(DEFAULT_CONFIG)

with open(path, encoding="utf-8") as config_file:
loaded = yaml.safe_load(config_file) or {}

if not isinstance(loaded, dict):
return copy.deepcopy(DEFAULT_CONFIG)
return _merge_config(DEFAULT_CONFIG, loaded)


def config_get(*keys, default=None):
value = load_config()
for key in keys:
if not isinstance(value, dict) or key not in value:
return default
value = value[key]
return value
30 changes: 30 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copy this file to config.yaml and adjust local values.
# config.yaml is intentionally git-ignored so local secrets/device settings are not overwritten.

mqtt:
host: localhost
port: 1883
keepalive: 60

door:
mqtt:
host: mqtt.space.hack42.nl
port: 1883
keepalive: 60
topic: hack42/brandhok/deuropen

stickers:
printer:
model: QL-710W
host: BRW008092D4A414.space.hack42.nl
port: 9100
# Optional override when the brother_ql backend needs a full identifier:
# identifier: tcp://BRW008092D4A414.space.hack42.nl:9100

pos:
serial:
port: /dev/ttyUSB0
baudrate: 19200

logging:
level: INFO
43 changes: 43 additions & 0 deletions input_validation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import re


RESERVED_INPUTS = {"abort", "ok"}
INPUT_TOKEN_RE = re.compile(r"^[A-Za-z0-9]+$")


def reserved_inputs(master=None, plugin_help=None):
reserved = set(RESERVED_INPUTS)
if isinstance(plugin_help, dict):
reserved.update(plugin_help.keys())
master_help = getattr(master, "help", {})
if isinstance(master_help, dict):
reserved.update(master_help.keys())
return reserved


def is_reserved_input(text, master=None, plugin_help=None):
return text in reserved_inputs(master=master, plugin_help=plugin_help)


def filter_reserved_aliases(aliases, master=None, plugin_help=None):
return [
alias
for alias in aliases
if not is_reserved_input(alias, master=master, plugin_help=plugin_help)
]


def is_valid_input_token(text, min_length):
return (
isinstance(text, str)
and len(text) >= min_length
and INPUT_TOKEN_RE.fullmatch(text) is not None
)


def is_valid_alias(text):
return is_valid_input_token(text, min_length=6)


def is_valid_product_name(text):
return is_valid_input_token(text, min_length=4)
Loading
Loading