24 Commits

Author SHA1 Message Date
localhorst aa3f641bf2 add report generator script 2026-04-21 22:26:30 +02:00
localhorst d4f89f39bd add merge tool 2026-04-21 21:41:17 +02:00
localhorst 91a6c4a304 Merge pull request 'Update dependencies and build system' (#2) from feature/revival into master
Reviewed-on: #2
2026-04-03 21:06:54 +02:00
localhorst ed648a2740 add clear all positions in order draft 2026-03-07 20:33:13 +01:00
localhorst 6330d7fd51 fix missing xml config 2026-03-01 19:10:07 +01:00
localhorst c241964a8b Configure auto Print or manual Print 2026-03-01 18:52:19 +01:00
localhorst f3e0da2689 fix ArrayIndexOutOfBoundsException 2026-02-25 22:02:46 +01:00
localhorst 4421075bec update version 2026-02-25 21:43:52 +01:00
localhorst 71c79575c8 update dependencies 2026-02-25 21:41:14 +01:00
localhorst 1866782111 update java.math.RoundingMode 2026-02-25 21:18:04 +01:00
localhorst a7e76e00d3 remove dead code 2026-02-25 21:13:21 +01:00
localhorst 1cadfe5bfc code formatter 2026-02-21 18:32:20 +01:00
localhorst c40be1c203 fix first start 2026-02-21 18:22:25 +01:00
localhorst 02026633ed update to new versions and build system 2026-02-17 19:04:16 +01:00
localhorst c4297a4be2 fixed spelling mistakes 2019-01-10 21:54:46 +01:00
localhorst fd52d3df6d added yt link for Kurzanleitung 2019-01-09 22:53:07 +01:00
localhorst c320ef615d added yt link and fixed some spelling errors 2019-01-09 18:39:47 +01:00
localhorst 2a20ea7bdb spelling errors fixed 2019-01-06 17:44:25 +01:00
localhorst a56ad43852 Merge branch 'master' of https://git.mosad.xyz/localhorst/jFxKasse 2018-12-08 13:21:35 +01:00
localhorst 188214dfbe bug fix 2018-12-08 13:21:14 +01:00
localhorst cdbe2cccc0 „README.md“ ändern 2018-12-07 23:45:41 +01:00
localhorst 5152846478 added screenshots 2018-12-07 23:31:12 +01:00
localhorst f5c5d546c5 added kiy input 2018-12-07 23:23:48 +01:00
localhorst 5c9d54dabf ui fixes and bill layout improvements 2018-12-07 11:25:45 +01:00
33 changed files with 4000 additions and 2675 deletions
+4
View File
@@ -0,0 +1,4 @@
target/
dependency-reduced-pom.xml
tools/*.db
tools/*.html
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<profiles version="21">
<profile kind="CodeFormatterProfile" name="jFxKasse" version="21">
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="tab"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation" value="do not insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" value="end_of_line"/>
<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" value="1"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
</profile>
</profiles>
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"vscjava.vscode-java-pack",
"vscjava.vscode-maven",
"redhat.java"
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Run jFxKasse",
"request": "launch",
"mainClass": "com.jFxKasse.application.JavaFX11Main",
"projectName": "jFxKasse",
"vmArgs": "--module-path ${env:PATH_TO_FX} --add-modules javafx.controls,javafx.fxml --add-opens javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED"
},
{
"type": "java",
"name": "Run jFxKasse (Maven)",
"request": "launch",
"mainClass": "com.jFxKasse.application.JavaFX11Main",
"projectName": "jFxKasse"
}
]
}
+18
View File
@@ -0,0 +1,18 @@
{
"java.project.sourcePaths": [
"src/main/java"
],
"java.project.outputPath": "target/classes",
"java.project.referencedLibraries": [],
"java.configuration.updateBuildConfiguration": "automatic",
"java.compile.nullAnalysis.mode": "automatic",
"editor.formatOnSave": true,
"files.encoding": "utf8",
// Java formatter: point to workspace-local Eclipse formatter profile
"java.format.settings.url": ".vscode/eclipse-formatter.xml",
"java.format.settings.profile": "jFxKasse",
"[java]": {
"editor.defaultFormatter": "redhat.java"
}
}
+51
View File
@@ -0,0 +1,51 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "mvn compile",
"type": "shell",
"command": "mvn compile",
"group": "build",
"problemMatcher": [
"$javac"
]
},
{
"label": "mvn package",
"type": "shell",
"command": "mvn package -DskipTests",
"group": {
"kind": "build",
"isDefault": true
},
"problemMatcher": [
"$javac"
]
},
{
"label": "mvn clean",
"type": "shell",
"command": "mvn clean",
"group": "build",
"problemMatcher": []
},
{
"label": "mvn javafx:run",
"type": "shell",
"command": "mvn javafx:run",
"group": "build",
"problemMatcher": [
"$javac"
]
},
{
"label": "mvn test",
"type": "shell",
"command": "mvn test",
"group": "test",
"problemMatcher": [
"$javac"
]
}
]
}
+81 -1
View File
@@ -1,2 +1,82 @@
# jFxKasse
easy payment system for small to middel sized events with a sales slip printer
Simple POS (Point of Sale) system for small to medium-sized events with receipt printer support.
## Requirements
- Java JDK 21+
- Maven 3.9+
- Display >= 1366x768
## Video Tutorials (German | Deutsch)
[YouTube Video: jFxKasse - Kassensystem - Kurzanleitung](https://www.youtube.com/watch?v=DV9DDESw40I)
[YouTube Video: jFxKasse - Kassensystem - Installieren](https://www.youtube.com/watch?v=IY1bqRjwh0Q)
## Setup (VSCode on openSUSE Tumbleweed)
### 1. Install dependencies
```bash
sudo zypper install java-21-openjdk java-21-openjdk-devel maven
```
### 2. Install VSCode extensions
Open the project in VSCode. You will be prompted to install the recommended extensions:
- **Extension Pack for Java** (`vscjava.vscode-java-pack`)
- **Maven for Java** (`vscjava.vscode-maven`)
- **Language Support for Java** (`redhat.java`)
### 3. Build & Run
```bash
# Compile
mvn compile
# Run via Maven
mvn javafx:run
# Package as fat JAR
mvn package
java -jar target/jFxKasse-shaded.jar
```
Or use the preconfigured VSCode tasks (`Ctrl+Shift+B`) and launch configurations (`F5`).
## Screenshots
| Main View | Jobs | Positions | Settings |
|-----------|------|-----------|----------|
| ![](screenshots/newjob) | ![](screenshots/jobs) | ![](screenshots/positions) | ![](screenshots/settings) |
## Hardware
Tested with: [Epson TM T20II](https://www.epson.de/products/sd/pos-printer/epson-tm-t20ii). Other receipt printers should work as well.
## Tools
### merge_db
Merges multiple SQLite databases into one file with automatic deduplication.
Reference tables (`category`, `positionen`) are deduplicated by content; `jobs` rows are appended in order with re-sequenced IDs to avoid conflicts.
```bash
python tools/merge_db.py -o OUTPUT_DB INPUT_DB [INPUT_DB ...]
# Example
python tools/merge_db.py -o merged.db 02.db 03.db 04.db
```
### generate_report
Generates a German HTML + PDF report from a [jFxKasse](https://git.mosad.xyz/localhorst/jFxKasse) SQLite database, including category/item tables, order KPIs, revenue breakdowns, and hourly trend charts per category.
```bash
python tools/generate_report.py kassendaten.db # → kassendaten_bericht.html
python tools/generate_report.py kassendaten.db -o out.html
```
## License
GPL-3.0
+36 -21
View File
@@ -1,42 +1,49 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com</groupId>
<artifactId>jFxKasse</artifactId>
<version>0.3.0</version>
<version>0.4.0</version>
<name>jFxKasse</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<maven.compiler.release>21</maven.compiler.release>
<javafx.version>23.0.1</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>6.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>11</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>11</version>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.jfoenix</groupId>
<artifactId>jfoenix</artifactId>
<version>9.0.8</version>
<version>9.0.10</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.23.1</version>
<version>3.51.2.0</version>
</dependency>
</dependencies>
@@ -46,11 +53,11 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<version>3.15.0</version>
<configuration>
<source>11</source>
<target>11</target>
<!--<release>11</release> -->
<source>21</source>
<target>21</target>
<release>21</release>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
@@ -59,7 +66,7 @@
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.6.0</version>
<version>3.6.3</version>
<executions>
<execution>
<goals>
@@ -71,17 +78,16 @@
<mainClass>com.jFxKasse.application.Main</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>3.6.1</version>
<configuration>
<finalName>jFxKasse</finalName>
<shadedArtifactAttached>true</shadedArtifactAttached>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.jFxKasse.application.JavaFX11Main</mainClass>
</transformer>
</transformers>
@@ -95,7 +101,16 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.jFxKasse.application.JavaFX11Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
</project>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

@@ -1,12 +1,8 @@
package com.jFxKasse.application;
import com.jFxKasse.application.Main;
public class JavaFX11Main {
public class JavaFX11Main
{
public static void main(String[] args)
{
Main.main(args);
public static void main(String[] args) {
Main.main(args);
}
}
+153 -119
View File
@@ -1,119 +1,153 @@
package com.jFxKasse.application;
import javafx.application.Application;
import java.io.File;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.util.Duration;
import com.jFxKasse.controller.MainWindowController;
import com.jFxKasse.controller.PrinterController;
import com.jFxKasse.controller.XMLController;
import com.jFxKasse.controller.DBController;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application
{
// path to config.xml and the database
public static String filepath;
private static String osName = System.getProperty("os.name");
private static String userHome = System.getProperty("user.home");
private MainWindowController mwc;
private XMLController xmlc = new XMLController(filepath);
private DBController dbc = new DBController(filepath);
private PrinterController pc = new PrinterController();
private Stage primaryStage;
@Override
public void start(Stage primaryStage)
{
this.primaryStage = primaryStage;
System.out.println("\nstarting jFxKasse\n");
mainWindow();
}
private void mainWindow()
{
try {
FXMLLoader loader = new FXMLLoader(
getClass().getResource("/fxml/MainWindow.fxml"));
AnchorPane pane = loader.load();
primaryStage.setTitle("jFxKasse"); // Title of window
mwc = loader.getController(); // set the mwc as the JavaFx
// MainWindowController
pc.searchPrinters(); // search for available printers
mwc.setMain(this, dbc, xmlc, pc); // set the created instances to the
// mwc
firstStart(); // test if this is the first run
Scene scene = new Scene(pane);
scene.getStylesheets().add(
Main.class.getResource("/css/application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show(); // shows the stage
Timeline timeline = new Timeline(
new KeyFrame(Duration.seconds(1), ev -> {
mwc.updateTimeLabel(); // update time label on UI
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args)
{
if (osName.contains("Windows")) {
System.out.println("FCK Windows");
filepath = userHome + "/Documents/jFxKasse/";
} else {
filepath = userHome + "/jFxKasse/";
}
launch(args);
}
/**
* Checks if the config.xml is preset.
*/
private void firstStart() throws Exception
{
if (xmlc.loadSettings()) {
// config.xml found, app starting normal
System.out.println("XML found!");
mwc.initUI(); // Starting the UI elements
mwc.setDBLabel(); // Set databese labels
dbc.setDbname(xmlc.getDatabaseName()); // handover database name
dbc.connectDatabase(); // estabishing DB conection
mwc.fillTablePositionen(); // fill TreeTable 'Positionen'
mwc.fillCategory();
mwc.fillPrinterSettings();
mwc.fillTableJobs();
mwc.loadGridButtons();
mwc.getSelectedCat(); // Load DB entries in Chois Box
mwc.createNewJob();
} else {
// config.xml NOT found, first start of app
System.out.println("no XML found!");
xmlc.initXML(); // set default values
mwc.blockUI(true); // disable UI elements that need DB
mwc.blockUnlock();
File dir = new File(filepath);
dir.mkdir(); // Create new Subfolder
}
}
}
package com.jFxKasse.application;
import com.jFxKasse.controller.MainWindowController;
import com.jFxKasse.controller.PrinterController;
import com.jFxKasse.controller.XMLController;
import com.jFxKasse.controller.DBController;
import com.jFxKasse.controller.KeyController;
import javafx.application.Application;
import java.io.File;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.fxml.FXMLLoader;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
public class Main extends Application {
// Path to config.xml and the database
public static String filepath;
private static String osName = System.getProperty("os.name");
private static String userHome = System.getProperty("user.home");
private MainWindowController mwc;
// Initialized in start() after filepath is set by main()
private XMLController xmlc;
private DBController dbc;
private PrinterController pc = new PrinterController();
private KeyController kc;
private Stage primaryStage;
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
// filepath is set by main() before launch(); initialize controllers here
xmlc = new XMLController(filepath);
dbc = new DBController(filepath);
System.out.println("\nstarting jFxKasse\n");
mainWindow();
}
private void mainWindow() {
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/MainWindow.fxml"));
AnchorPane pane = loader.load();
primaryStage.setTitle("jFxKasse"); // Window title
mwc = loader.getController(); // Set the MainWindowController
pc.searchPrinters(); // Search for available printers
mwc.setMain(this, dbc, xmlc, pc); // Pass instances to the controller
firstStart(); // Check if this is the first run
Scene scene = new Scene(pane);
scene.getStylesheets().add(Main.class.getResource("/css/application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show(); // Show the stage
// Attach the KeyController
kc = new KeyController(scene, mwc);
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1), ev -> {
mwc.updateTimeLabel(); // Update time label on UI
}));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
}
catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if (osName.contains("Windows")) {
System.out.println("FCK Windows");
filepath = userHome + "/Documents/jFxKasse/";
}
else {
filepath = userHome + "/jFxKasse/";
}
launch(args);
}
/**
* Checks if the config.xml is present and initializes accordingly.
*/
private void firstStart() throws Exception {
if (xmlc.loadSettings()) {
// config.xml found, app starting normally
System.out.println("XML found!");
mwc.initUI(); // Initialize the UI elements
mwc.setDBLabel(); // Set database labels
dbc.setDbname(xmlc.getDatabaseName()); // Pass database name
try {
dbc.connectDatabase(); // Establish DB connection
}
catch (Exception e) {
e.printStackTrace();
showErrorDialog("Datenbankfehler", "Die Datenbank konnte nicht geöffnet werden.",
"Datei: " + xmlc.getDatabaseName() + ".db\n\n"
+ "Bitte prüfen Sie, ob die Datei vorhanden und nicht beschädigt ist.\n"
+ "Fehlermeldung: " + e.getMessage());
return;
}
mwc.fillTablePositionen(); // Fill TreeTable 'Positions'
mwc.fillCategory();
mwc.fillPrinterSettings();
mwc.fillTableJobs();
mwc.loadGridButtons();
mwc.getSelectedCat(); // Load DB entries in ChoiceBox
mwc.createNewJob();
}
else {
// config.xml NOT found — first start of app
System.out.println("no XML found!");
xmlc.initXML(); // Set default values in memory
File dir = new File(filepath);
dir.mkdir(); // Create config directory
try {
xmlc.saveSettings(); // Persist defaults immediately so next start is normal
System.out.println("Default config.xml written.");
}
catch (Exception e) {
e.printStackTrace();
showErrorDialog("Konfigurationsfehler", "Die Konfigurationsdatei konnte nicht geschrieben werden.",
"Pfad: " + filepath + "config.xml\n\n" + "Bitte prüfen Sie die Schreibrechte.\n"
+ "Fehlermeldung: " + e.getMessage());
}
mwc.blockUI(true); // Disable UI elements that need a DB
mwc.blockUnlock(); // Show first-start hint, disable lock button
}
}
/** Shows a modal error alert dialog on the JavaFX thread. */
private void showErrorDialog(String title, String header, String content) {
javafx.scene.control.Alert alert = new javafx.scene.control.Alert(javafx.scene.control.Alert.AlertType.ERROR);
alert.setTitle(title);
alert.setHeaderText(header);
alert.setContentText(content);
alert.setResizable(true);
alert.showAndWait();
}
}
@@ -16,25 +16,20 @@ import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.util.Pair;
public class PrintJob
{
public class PrintJob {
private TimeDate timedate = new TimeDate();
public void printJob(int jobID, XMLController xmlc, DBController dbc,
PrinterController pc)
{
public void printJob(int jobID, XMLController xmlc, DBController dbc, PrinterController pc) {
if ((xmlc.getPrintername().equals("Drucker auswählen")
|| xmlc.getPrintername() == null)) {
// no printer selected
System.out.println("Kein Drucker eingestellt!!!");
if ((xmlc.getPrintername().equals("Drucker auswählen") || xmlc.getPrintername() == null)) {
// No printer selected
System.out.println("No printer configured!");
// creates a dialog
Dialog<Pair<String, String>> dialog = new Dialog<>();
dialog.setTitle("Kein Drucker");
dialog.setHeaderText("Es ist kein Drucker einestellt.\n"
+ "In den Einstellungen einen Drucker auswählen.");
dialog.setHeaderText("Es ist kein Drucker einestellt.\n" + "In den Einstellungen einen Drucker auswählen.");
dialog.getDialogPane().getButtonTypes().addAll(ButtonType.OK);
@@ -43,30 +38,29 @@ public class PrintJob
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
grid.add(new Label("Tipp:\n"
+ "Es kann ein virtueller Drucker installiert werden: \n\n"
+ "www.cups-pdf.de"), 0, 0);
grid.add(
new Label(
"Tipp:\n" + "Es kann ein virtueller Drucker installiert werden: \n\n" + "www.cups-pdf.de"),
0, 0);
dialog.getDialogPane().setContent(grid);
dialog.setResizable(true);
dialog.showAndWait();
} else {
// printer selected
}
else {
// Printer selected
pc.selectPrinter(xmlc.getPrintername());
/* Single bill or splitted */
/* Single bill or split by category */
if (xmlc.getCategorySplitted()) {
// split the bills
// Split the bills
PrintDataSplitted pdsplitted = new PrintDataSplitted(
xmlc.getLinebreak(), xmlc.getOffsetHeader(),
xmlc.getOffsetFooter(),
timedate.getSystemTime() + " " + timedate.getSystemDate(),
PrintDataSplitted pdsplitted = new PrintDataSplitted(xmlc.getLinebreak(), xmlc.getOffsetHeader(),
xmlc.getOffsetFooter(), timedate.getSystemTime() + " " + timedate.getSystemDate(),
xmlc.getHeader(), xmlc.getFooter());
pdsplitted.setData(Integer.toString(jobID), dbc.getTime_Job(jobID),
dbc.getQuantity_Job(jobID), dbc.getName_Job(jobID),
dbc.getValue_Job(jobID), dbc.getCategory_Job(jobID),
pdsplitted.setData(Integer.toString(jobID), dbc.getTime_Job(jobID), dbc.getQuantity_Job(jobID),
dbc.getName_Job(jobID), dbc.getValue_Job(jobID), dbc.getCategory_Job(jobID),
dbc.getJobValue_Job(jobID));
System.out.println("Printing job ...");
@@ -77,16 +71,15 @@ public class PrintJob
pc.printString(printString.get(i));
}
} else {
// one single bills
PrintDataSimple pds = new PrintDataSimple(xmlc.getLinebreak(),
xmlc.getOffsetHeader(), xmlc.getOffsetFooter(),
timedate.getSystemTime() + " " + timedate.getSystemDate(),
}
else {
// Single bill
PrintDataSimple pds = new PrintDataSimple(xmlc.getLinebreak(), xmlc.getOffsetHeader(),
xmlc.getOffsetFooter(), timedate.getSystemTime() + " " + timedate.getSystemDate(),
xmlc.getHeader(), xmlc.getFooter());
pds.setData(Integer.toString(jobID), dbc.getTime_Job(jobID),
dbc.getQuantity_Job(jobID), dbc.getName_Job(jobID),
dbc.getValue_Job(jobID), dbc.getCategory_Job(jobID),
pds.setData(Integer.toString(jobID), dbc.getTime_Job(jobID), dbc.getQuantity_Job(jobID),
dbc.getName_Job(jobID), dbc.getValue_Job(jobID), dbc.getCategory_Job(jobID),
dbc.getJobValue_Job(jobID));
System.out.println("Printing job ...");
pc.printString(pds.getPrintString());
@@ -4,18 +4,15 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class TimeDate
{
public String getSystemTime()
{
public class TimeDate {
public String getSystemTime() {
DateFormat dateFormat = new SimpleDateFormat("HH:mm");
Date date = new Date();
String time = dateFormat.format(date);
return time;
}
public String getSystemDate()
{
public String getSystemDate() {
DateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy");
Date date = new Date();
String dateStr = dateFormat.format(date);
@@ -11,83 +11,77 @@ import com.jFxKasse.datatypes.tableDataJob;
import com.jFxKasse.datatypes.tableDataPositionen;
import java.io.File;
public class DBController
{
public class DBController {
private Connection connection;
private String DB_PATH;
private String dbname;
public void main()
{
public void main() {
try {
connection = DriverManager
.getConnection("jdbc:sqlite:" + DB_PATH + dbname + ".db");
} catch (SQLException e) {
connection = DriverManager.getConnection("jdbc:sqlite:" + DB_PATH + dbname + ".db");
}
catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public DBController(String path)
{
public DBController(String path) {
this.DB_PATH = path;
}
public void setDbname(String dbname)
{
public void setDbname(String dbname) {
this.dbname = dbname;
}
public void connectDatabase()
{ // connect to database
public void connectDatabase() { // Connect to database
System.out.println("Connecting... DB name: " + dbname);
try {
if (connection != null)
return;
connection = DriverManager
.getConnection("jdbc:sqlite:" + DB_PATH + dbname + ".db");
connection = DriverManager.getConnection("jdbc:sqlite:" + DB_PATH + dbname + ".db");
if (!connection.isClosed())
System.out.println("DB connection established");
} catch (SQLException e) {
}
catch (SQLException e) {
throw new RuntimeException(e);
}
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run()
{
public void run() {
try {
if (!connection.isClosed() && connection != null) {
connection.close();
if (connection.isClosed())
System.out.println();
}
} catch (SQLException e) {
}
catch (SQLException e) {
e.printStackTrace();
}
}
});
}
public boolean existDB(String pPfad)
{ // does the DB exists?
public boolean existDB(String pPfad) { // Check if the DB exists
File varTmpDir = new File(pPfad);
if (!varTmpDir.exists()) {
return false;
} else {
}
else {
return true;
}
}
public String getCategoryNameFromPositionen(int pID)
{
public String getCategoryNameFromPositionen(int pID) {
int catInPos = 0;
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT posid, cat FROM positionen "
+ "WHERE posid = " + pID + ";");
ResultSet rs = stmt.executeQuery("SELECT posid, cat FROM positionen " + "WHERE posid = " + pID + ";");
catInPos = rs.getInt("cat");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
@@ -97,10 +91,11 @@ public class DBController
}
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT catid, catname FROM category "
+ "WHERE catid = " + catInPos + ";");
ResultSet rs = stmt
.executeQuery("SELECT catid, catname FROM category " + "WHERE catid = " + catInPos + ";");
return rs.getString("catname");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "keine Kategorie";
@@ -108,34 +103,31 @@ public class DBController
}
// table Position section //
public void createTablePositionen()
{ // create table position
// Table 'Positionen' section //
public void createTablePositionen() { // Create table 'positionen'
System.out.println("Creating table Positionen");
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("DROP TABLE IF EXISTS positionen;");
stmt.executeUpdate(
"CREATE TABLE positionen (posid, name, value, cat, color);");
} catch (SQLException e) {
stmt.executeUpdate("CREATE TABLE positionen (posid, name, value, cat, color);");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
// create 25 demo/default data entries
// Create 25 demo/default data entries
for (int i = 0; i < 25; i++) {
fillPositionen_Positionen(i + 1, "Noch frei", (float) 0.00, 6,
"#ad0000");
fillPositionen_Positionen(i + 1, "Noch frei", (float) 0.00, 6, "#ad0000");
}
}
public void fillPositionen_Positionen(int pID, String pName, float pValue,
int pCat, String pColor)
{ // create new data in table
public void fillPositionen_Positionen(int pID, String pName, float pValue, int pCat, String pColor) { // Create new
// entry in
// table
System.out.println("Creating new positionen entry");
try {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO positionen VALUES (?, ?, ?, ?, ?);");
PreparedStatement ps = connection.prepareStatement("INSERT INTO positionen VALUES (?, ?, ?, ?, ?);");
ps.setInt(1, pID); // primary
ps.setString(2, pName);
ps.setFloat(3, pValue);
@@ -146,144 +138,134 @@ public class DBController
connection.setAutoCommit(false);
ps.executeBatch(); // SQL execution
connection.setAutoCommit(true);
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public String getName_Positionen(int pID)
{
public String getName_Positionen(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT posid, name FROM positionen WHERE posid = " + pID + ";");
ResultSet rs = stmt.executeQuery("SELECT posid, name FROM positionen WHERE posid = " + pID + ";");
return rs.getString("name");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "Error 404";
}
}
public String getValue_Positionen(int pID)
{
public String getValue_Positionen(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT posid, value FROM positionen WHERE posid = " + pID
+ ";");
ResultSet rs = stmt.executeQuery("SELECT posid, value FROM positionen WHERE posid = " + pID + ";");
return rs.getString("value");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "Error 404";
}
}
public int getCat_Positionen(int pID)
{
public int getCat_Positionen(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT catid, cat FROM positionen WHERE catid = " + pID + ";");
ResultSet rs = stmt.executeQuery("SELECT catid, cat FROM positionen WHERE catid = " + pID + ";");
System.out.println("getCarTet: " + rs.getInt("cat"));
return rs.getInt("cat");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return 0;
}
}
public String getColor_Positionen(int pID)
{
public String getColor_Positionen(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT posid, color FROM positionen WHERE posid = " + pID
+ ";");
ResultSet rs = stmt.executeQuery("SELECT posid, color FROM positionen WHERE posid = " + pID + ";");
return rs.getString("color");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "Error 404";
}
}
public void setName_Positionen(int pID, String pName)
{
public void setName_Positionen(int pID, String pName) {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE positionen SET name = '" + pName
+ "'WHERE posid =" + pID + ";");
} catch (SQLException e) {
stmt.executeUpdate("UPDATE positionen SET name = '" + pName + "'WHERE posid =" + pID + ";");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public void setValue_Positionen(int pID, String pValue)
{
public void setValue_Positionen(int pID, String pValue) {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE positionen SET value = '" + pValue
+ "'WHERE posid =" + pID + ";");
} catch (SQLException e) {
stmt.executeUpdate("UPDATE positionen SET value = '" + pValue + "'WHERE posid =" + pID + ";");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public void setCat_Positionen(int pID, int pCat)
{
public void setCat_Positionen(int pID, int pCat) {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE positionen SET cat = '" + pCat
+ "'WHERE posid =" + pID + ";");
} catch (SQLException e) {
stmt.executeUpdate("UPDATE positionen SET cat = '" + pCat + "'WHERE posid =" + pID + ";");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public void setColor_Positionen(int pID, String pColor)
{
public void setColor_Positionen(int pID, String pColor) {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE positionen SET color = '" + pColor
+ "'WHERE posid =" + pID + ";");
} catch (SQLException e) {
stmt.executeUpdate("UPDATE positionen SET color = '" + pColor + "'WHERE posid =" + pID + ";");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public ArrayList<tableDataPositionen> ladeTabellePositionen()
{
public ArrayList<tableDataPositionen> ladeTabellePositionen() {
ArrayList<tableDataPositionen> daten = new ArrayList<>();
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM positionen;");
while (rs.next()) {
try {
daten.add(new tableDataPositionen(rs.getInt("posid"),
rs.getString("name"), rs.getString("value"),
daten.add(new tableDataPositionen(rs.getInt("posid"), rs.getString("name"), rs.getString("value"),
rs.getString("cat"), rs.getString("color")));
} catch (Exception e) {
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
return daten;
}
public void ausgebenSysoPositionen()
{
public void ausgebenSysoPositionen() {
System.out.println("Print positionen");
try {
Statement stmt = connection.createStatement();
@@ -296,21 +278,22 @@ public class DBController
System.out.println(" ");
}
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
// table Category section //
public void createTableCategory()
{ // create table position
System.out.println("Erstelle Tabelle Kategorie");
// Table 'Category' section //
public void createTableCategory() { // Create table 'category'
System.out.println("Creating table Category");
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("DROP TABLE IF EXISTS category;");
stmt.executeUpdate("CREATE TABLE category (catid, catname);");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
@@ -323,76 +306,71 @@ public class DBController
}
public void setName_Category(int pID, String pName)
{
public void setName_Category(int pID, String pName) {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE category SET catname = '" + pName
+ "'WHERE catid =" + pID + ";");
} catch (SQLException e) {
stmt.executeUpdate("UPDATE category SET catname = '" + pName + "'WHERE catid =" + pID + ";");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public void fillCategory_Category(int pID, String pName)
{
public void fillCategory_Category(int pID, String pName) {
System.out.println("Erstelle neuen Kategorie Eintrag");
System.out.println("Creating new category entry");
try {
PreparedStatement ps = connection
.prepareStatement("INSERT INTO category VALUES (?, ?);");
PreparedStatement ps = connection.prepareStatement("INSERT INTO category VALUES (?, ?);");
ps.setInt(1, pID); // primary
ps.setString(2, pName);
ps.addBatch();
connection.setAutoCommit(false);
ps.executeBatch(); // SQL execution
connection.setAutoCommit(true);
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public String getName_Category(int pID)
{
public String getName_Category(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT catid, catname FROM category WHERE catid = " + pID
+ ";");
ResultSet rs = stmt.executeQuery("SELECT catid, catname FROM category WHERE catid = " + pID + ";");
return rs.getString("catname");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "Error 404";
}
}
// table Jobs section //
public void erstelleTabelleJobs()
{ // create table jobs
// Table 'Jobs' section //
public void erstelleTabelleJobs() { // Create table 'jobs'
System.out.println("Creating table Jobs");
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("DROP TABLE IF EXISTS jobs;");
stmt.executeUpdate(
"CREATE TABLE jobs (jobid, time, positionen_quantity, positionen_name, positionen_value, positionen_cat, state, jobvalue);");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public int getLatestJobNumber_Job()
{
public int getLatestJobNumber_Job() {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid from jobs ORDER BY jobid DESC LIMIT 1;");
ResultSet rs = stmt.executeQuery("SELECT jobid from jobs ORDER BY jobid DESC LIMIT 1;");
return rs.getInt("jobid");
} catch (SQLException e) {
}
catch (SQLException e) {
// System.err.println("Couldn't handle DB-Query");
// e.printStackTrace();
@@ -400,152 +378,138 @@ public class DBController
return 0;
}
public String getTime_Job(int pID)
{
public String getTime_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, time FROM jobs WHERE jobid = " + pID + ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, time FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("time");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getQuantity_Job(int pID)
{
public String getQuantity_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, positionen_quantity FROM jobs WHERE jobid = "
+ pID + ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, positionen_quantity FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("positionen_quantity");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getName_Job(int pID)
{
public String getName_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, positionen_name FROM jobs WHERE jobid = " + pID
+ ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, positionen_name FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("positionen_name");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getValue_Job(int pID)
{
public String getValue_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, positionen_value FROM jobs WHERE jobid = " + pID
+ ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, positionen_value FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("positionen_value");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getCategory_Job(int pID)
{
public String getCategory_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, positionen_cat FROM jobs WHERE jobid = " + pID
+ ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, positionen_cat FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("positionen_cat");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getState_Job(int pID)
{
public String getState_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, state FROM jobs WHERE jobid = " + pID + ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, state FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("state");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getJobValue_Job(int pID)
{
public String getJobValue_Job(int pID) {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT jobid, jobvalue FROM jobs WHERE jobid = " + pID + ";");
ResultSet rs = stmt.executeQuery("SELECT jobid, jobvalue FROM jobs WHERE jobid = " + pID + ";");
return rs.getString("jobvalue");
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "404";
}
}
public String getAllJobValue_Job()
{
public String getAllJobValue_Job() {
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT state, jobvalue, SUM(jobvalue) AS ALLVALUE FROM jobs WHERE state = "
+ '"' + "verbucht" + '"' + ";");
return rs.getString("ALLVALUE");
} catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "0";
}
}
public String getJobCount()
{
try {
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT count(*) AS JOBCOUNT FROM jobs WHERE state = " + '"'
ResultSet rs = stmt
.executeQuery("SELECT state, jobvalue, SUM(jobvalue) AS ALLVALUE FROM jobs WHERE state = " + '"'
+ "verbucht" + '"' + ";");
return rs.getString("JOBCOUNT");
} catch (SQLException e) {
return rs.getString("ALLVALUE");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "0";
}
}
public void setStatus_Jobs(int pID, String pStatus)
{
public String getJobCount() {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE jobs SET state = '" + pStatus
+ "'WHERE jobid =" + pID + ";");
} catch (SQLException e) {
ResultSet rs = stmt.executeQuery(
"SELECT count(*) AS JOBCOUNT FROM jobs WHERE state = " + '"' + "verbucht" + '"' + ";");
return rs.getString("JOBCOUNT");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
return "0";
}
}
public void setStatus_Jobs(int pID, String pStatus) {
try {
Statement stmt = connection.createStatement();
stmt.executeUpdate("UPDATE jobs SET state = '" + pStatus + "'WHERE jobid =" + pID + ";");
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
public ArrayList<tableDataJob> loadTableJobs_Job()
{
public ArrayList<tableDataJob> loadTableJobs_Job() {
ArrayList<tableDataJob> tmp = new ArrayList<tableDataJob>();
try {
@@ -557,31 +521,29 @@ public class DBController
String tablePosition = rs.getString("positionen_name");
tableDataJob data = new tableDataJob(rs.getInt("jobid"),
rs.getString("time"), tablePosition, rs.getString("state"),
rs.getString("jobvalue"));
tableDataJob data = new tableDataJob(rs.getInt("jobid"), rs.getString("time"), tablePosition,
rs.getString("state"), rs.getString("jobvalue"));
tmp.add(data);
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
}
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
return tmp;
}
public void fillJobs_Jobs(int pID, String pTime, String pPositionen_quantity,
String pPositionen_name, String pPositionen_value,
String pPositionen_cat, String pState, String pJobvalue)
{
System.out.println("Create new Job Entry");
public void fillJobs_Jobs(int pID, String pTime, String pPositionen_quantity, String pPositionen_name,
String pPositionen_value, String pPositionen_cat, String pState, String pJobvalue) {
System.out.println("Creating new job entry");
try {
PreparedStatement ps = connection.prepareStatement(
"INSERT INTO jobs VALUES (?, ?, ?, ?, ?, ?, ?, ?);");
PreparedStatement ps = connection.prepareStatement("INSERT INTO jobs VALUES (?, ?, ?, ?, ?, ?, ?, ?);");
ps.setInt(1, pID); // primary
ps.setString(2, pTime);
ps.setString(3, pPositionen_quantity);
@@ -594,7 +556,8 @@ public class DBController
connection.setAutoCommit(false);
ps.executeBatch(); // SQL execution
connection.setAutoCommit(true);
} catch (SQLException e) {
}
catch (SQLException e) {
System.err.println("Couldn't handle DB-Query");
e.printStackTrace();
}
@@ -0,0 +1,212 @@
package com.jFxKasse.controller;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
public class KeyController {
private MainWindowController mwc;
public KeyController(Scene scene, MainWindowController mwc) {
this.mwc = mwc;
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
@Override
public void handle(KeyEvent keyEvent) {
switch (mwc.getActiveTab()) {
case 0:
handleTabNewJob(keyEvent);
break;
case 1:
handleTabJobs(keyEvent);
break;
case 2:
handleTabPosEdit(keyEvent);
break;
case 3:
handleTabSettings(keyEvent);
break;
default:
}
}
});
}
private void handleTabNewJob(KeyEvent key) {
if ((key.getCode() == KeyCode.ENTER) && (!mwc.btnPrintBill.isDisabled())) {
mwc.btnPrintBillAction(null);
}
if ((key.getCode() == KeyCode.ESCAPE) && (!mwc.btnLock.isDisabled())) {
mwc.btnLockAction(null);
}
if ((key.getCode() == KeyCode.DELETE) && (!mwc.btnDeleteSelectedPosition.isDisabled())) {
mwc.btnDeleteSelectedPositionAction(null);
}
handelGridButtons(key);
}
private void handleTabJobs(KeyEvent key) {
if ((key.getCode() == KeyCode.ENTER) && (!mwc.btnReprintJob.isDisabled())) {
mwc.btnReprintJobAction(null);
}
if ((key.getCode() == KeyCode.DELETE) && (!mwc.btnCancelJob.isDisabled())) {
mwc.btnCancelJobAction(null);
}
if ((key.getCode() == KeyCode.S) && (!mwc.btnCalcStats.isDisabled())) {
mwc.btnCalcStatsAction(null);
}
}
private void handleTabPosEdit(KeyEvent key) {
if ((key.getCode() == KeyCode.ENTER) && (!mwc.btnSaveEntry.isDisabled())) {
mwc.btnSaveEntryAction(null);
}
if ((key.getCode() == KeyCode.DELETE) && (!mwc.btnClearEntry.isDisabled())) {
mwc.btnClearEntryAction(null);
}
}
private void handleTabSettings(KeyEvent key) {
if ((key.getCode() == KeyCode.ENTER) && (!mwc.btnSavePrinter.isDisabled())) {
mwc.btnSavePrinterAction(null);
}
if ((key.getCode() == KeyCode.ENTER) && (!mwc.btnSaveCat.isDisabled())) {
mwc.btnSaveCatAction(null);
}
if ((key.getCode() == KeyCode.ENTER) && (!mwc.btnCreateNewDatabase.isDisabled())) {
try {
mwc.btnCreateNewDatabaseAction(null);
}
catch (Exception e) {
e.printStackTrace();
}
}
}
void handelGridButtons(KeyEvent key) {
switch (key.getCode()) {
case Q:
mwc.gridButton01Action(null);
break;
case W:
mwc.gridButton02Action(null);
break;
case E:
mwc.gridButton03Action(null);
break;
case R:
mwc.gridButton04Action(null);
break;
case T:
mwc.gridButton05Action(null);
break;
case Z:
mwc.gridButton06Action(null);
break;
case U:
mwc.gridButton07Action(null);
break;
case I:
mwc.gridButton08Action(null);
break;
case O:
mwc.gridButton09Action(null);
break;
case P:
mwc.gridButton10Action(null);
break;
case A:
mwc.gridButton11Action(null);
break;
case S:
mwc.gridButton12Action(null);
break;
case D:
mwc.gridButton13Action(null);
break;
case F:
mwc.gridButton14Action(null);
break;
case G:
mwc.gridButton15Action(null);
break;
case H:
mwc.gridButton16Action(null);
break;
case J:
mwc.gridButton17Action(null);
break;
case K:
mwc.gridButton18Action(null);
break;
case L:
mwc.gridButton19Action(null);
break;
case Y:
mwc.gridButton20Action(null);
break;
case X:
mwc.gridButton21Action(null);
break;
case C:
mwc.gridButton22Action(null);
break;
case V:
mwc.gridButton23Action(null);
break;
case B:
mwc.gridButton24Action(null);
break;
case N:
mwc.gridButton25Action(null);
break;
default:
break;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,5 @@
/**
* some parts are from http://www.mets-blog.com/java-pos-thermal-printer-example/
* Some parts are from http://www.mets-blog.com/java-pos-thermal-printer-example/
*/
package com.jFxKasse.controller;
@@ -21,26 +21,23 @@ import javax.print.SimpleDoc;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
public class PrinterController implements Printable
{
// All available Printers on this system
public class PrinterController implements Printable {
// All available printers on this system
private PrintService[] printService;
// selected printer
// Selected printer
private PrintService selectedPrinter;
private DocFlavor flavor;
public PrinterController()
{
public PrinterController() {
flavor = DocFlavor.BYTE_ARRAY.AUTOSENSE;
}
/**
* @return A String array with all available printers
*/
public String[] getAvailablePrinters()
{
public String[] getAvailablePrinters() {
int printerSize = PrinterJob.lookupPrintServices().length;
String printers[] = new String[printerSize];
@@ -51,10 +48,9 @@ public class PrinterController implements Printable
}
/**
* searchs connected printers on the system
* Searches for connected printers on the system
*/
public void searchPrinters()
{
public void searchPrinters() {
PrintRequestAttributeSet pras = new HashPrintRequestAttributeSet();
this.printService = PrintServiceLookup.lookupPrintServices(flavor, pras);
String printers[] = getAvailablePrinters();
@@ -67,11 +63,11 @@ public class PrinterController implements Printable
}
/**
* Selects the printer via its name
* Selects the printer by its name
*
* @param printerName
*/
public void selectPrinter(String printerName)
{
public void selectPrinter(String printerName) {
String printers[] = getAvailablePrinters();
for (int i = 0; i < printers.length; i++) {
@@ -84,11 +80,11 @@ public class PrinterController implements Printable
}
/**
* Prints a string to the selected printer
*
* @param input data as String
* @param text data as String
*/
public void printString(String text)
{
public void printString(String text) {
PrintService service = selectedPrinter;
DocPrintJob job = service.createPrintJob();
@@ -97,21 +93,21 @@ public class PrinterController implements Printable
bytes = text.getBytes(StandardCharsets.UTF_8);
Doc doc = new SimpleDoc(bytes, flavor, null);
job.print(doc, null);
} catch (Exception e) {
}
catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public int print(Graphics g, PageFormat pf, int page) throws PrinterException
{
public int print(Graphics g, PageFormat pf, int page) throws PrinterException {
if (page > 0) { /* We have only one page, and 'page' is zero-based */
return NO_SUCH_PAGE;
}
/*
* User (0,0) is typically outside the imageable area, so we must
* translate by the X and Y values in the PageFormat to avoid clipping
* User (0,0) is typically outside the imageable area, so we must translate by
* the X and Y values in the PageFormat to avoid clipping
*/
Graphics2D g2d = (Graphics2D) g;
g2d.translate(pf.getImageableX(), pf.getImageableY());
@@ -119,21 +115,20 @@ public class PrinterController implements Printable
return PAGE_EXISTS;
}
public void printBytes(byte[] bytes)
{
public void printBytes(byte[] bytes) {
PrintService service = selectedPrinter;
DocPrintJob job = service.createPrintJob();
try {
Doc doc = new SimpleDoc(bytes, flavor, null);
job.print(doc, null);
} catch (Exception e) {
}
catch (Exception e) {
e.printStackTrace();
}
}
public void cutPaper()
{
public void cutPaper() {
byte[] cutP = new byte[] { 0x1d, 'V', 1 };
printBytes(cutP);
}
@@ -7,8 +7,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.Properties;
public class XMLController
{
public class XMLController {
private String databaseName = null;
private String printername = null;
@@ -25,18 +24,18 @@ public class XMLController
private boolean categorySplitted;
private boolean autoPrintBill;
private String filePath = null;
private Properties props = null;
public XMLController(String filePath)
{
public XMLController(String filePath) {
this.filePath = filePath + "config.xml";
props = new Properties();
}
public void saveSettings() throws Exception
{ // Save settings to config.xml
public void saveSettings() throws Exception { // Save settings to config.xml
System.out.println("Saving XML");
@@ -50,10 +49,20 @@ public class XMLController
if (this.categorySplitted) {
categorySplitted = "true";
} else {
}
else {
categorySplitted = "false";
}
String autoPrintBill = null;
if (this.autoPrintBill) {
autoPrintBill = "true";
}
else {
autoPrintBill = "false";
}
try {
props.setProperty("databasename", this.databaseName);
props.setProperty("printername", this.printername);
@@ -63,16 +72,17 @@ public class XMLController
props.setProperty("header", this.header);
props.setProperty("footer", this.footer);
props.setProperty("categorySplitted", categorySplitted);
props.setProperty("autoPrintBill", autoPrintBill);
outputStream = new FileOutputStream(filePath);
props.storeToXML(outputStream, "jFxKasse settings");
outputStream.close();
} catch (IOException e) {
}
catch (IOException e) {
}
}
public boolean loadSettings() throws Exception
{ // reads the settings from config.xml
public boolean loadSettings() throws Exception { // Read settings from config.xml
InputStream inputStream;
try {
inputStream = new FileInputStream(filePath);
@@ -82,21 +92,22 @@ public class XMLController
try {
this.linebreak = Integer.valueOf(props.getProperty("linebreak"));
} catch (Exception e) {
}
catch (Exception e) {
this.linebreak = 28;
}
try {
this.offsetHeader = Integer
.valueOf(props.getProperty("offsetHeader"));
} catch (Exception e) {
this.offsetHeader = Integer.valueOf(props.getProperty("offsetHeader"));
}
catch (Exception e) {
this.offsetHeader = 1;
}
try {
this.offsetFooter = Integer
.valueOf(props.getProperty("offsetFooter"));
} catch (Exception e) {
this.offsetFooter = Integer.valueOf(props.getProperty("offsetFooter"));
}
catch (Exception e) {
this.offsetFooter = 2;
}
@@ -106,23 +117,37 @@ public class XMLController
try {
if (props.getProperty("categorySplitted").equals("true")) {
this.categorySplitted = true;
} else {
}
else {
this.categorySplitted = false;
}
} catch (Exception e) {
}
catch (Exception e) {
this.categorySplitted = false;
}
try {
if (props.getProperty("autoPrintBill").equals("true")) {
this.autoPrintBill = true;
}
else {
this.autoPrintBill = false;
}
}
catch (Exception e) {
this.autoPrintBill = false;
}
inputStream.close();
return true;
} catch (IOException e) {
}
catch (IOException e) {
e.printStackTrace();
return false;
}
}
public void initXML()
{
public void initXML() {
this.printername = "Drucker auswählen";
this.offsetHeader = 1;
@@ -132,88 +157,82 @@ public class XMLController
this.categorySplitted = false;
this.autoPrintBill = false;
this.header = "XYZ GmbH";
this.footer = "Vielen Dank für den Einkauf";
}
public String getDatabaseName()
{
public String getDatabaseName() {
return databaseName;
}
public void setDatabaseName(String databaseName)
{
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public String getPrintername()
{
public String getPrintername() {
return printername;
}
public void setPrintername(String printername)
{
public void setPrintername(String printername) {
this.printername = printername;
}
public int getLinebreak()
{
public int getLinebreak() {
return linebreak;
}
public void setLinebreak(int linebreak)
{
public void setLinebreak(int linebreak) {
this.linebreak = linebreak;
}
public int getOffsetHeader()
{
public int getOffsetHeader() {
return offsetHeader;
}
public void setOffsetHeader(int offsetHeader)
{
public void setOffsetHeader(int offsetHeader) {
this.offsetHeader = offsetHeader;
}
public int getOffsetFooter()
{
public int getOffsetFooter() {
return offsetFooter;
}
public void setOffsetFooter(int offsetFooter)
{
public void setOffsetFooter(int offsetFooter) {
this.offsetFooter = offsetFooter;
}
public String getHeader()
{
public String getHeader() {
return header;
}
public void setHeader(String header)
{
public void setHeader(String header) {
this.header = header;
}
public String getFooter()
{
public String getFooter() {
return footer;
}
public void setFooter(String footer)
{
public void setFooter(String footer) {
this.footer = footer;
}
public boolean getCategorySplitted()
{
public boolean getCategorySplitted() {
return categorySplitted;
}
public void setCategorySplitted(boolean categorySplitted)
{
public void setCategorySplitted(boolean categorySplitted) {
this.categorySplitted = categorySplitted;
}
public boolean getAutoPrintBill() {
return autoPrintBill;
}
public void setAutoPrintBill(boolean autoPrintBill) {
this.autoPrintBill = autoPrintBill;
}
}
@@ -1,32 +1,25 @@
package com.jFxKasse.datatypes;
public class Category
{
public class Category {
private String categoryName;
private String positionsString = "\n";
public Category(String categoryName)
{
public Category(String categoryName) {
this.categoryName = categoryName;
}
public String getCategoryName()
{
public String getCategoryName() {
return categoryName;
}
public void addPosition(int quantity, String name, String value,
PrintData pd)
{
public void addPosition(int quantity, String name, String value, PrintData pd) {
for (int i = 0; i < quantity; i++) {
positionsString = positionsString
+ pd.setRight(pd.breakLines(name), value + "") + "\n";
positionsString = positionsString + pd.setRight(pd.breakLines(name), value + "") + "\n";
}
}
public String getPositionsString()
{
public String getPositionsString() {
return positionsString;
}
}
+35 -42
View File
@@ -1,9 +1,10 @@
package com.jFxKasse.datatypes;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
public class Job
{
public class Job {
private int jobnumber;
private float jobvalue;
@@ -18,8 +19,7 @@ public class Job
private ArrayList<String> positionenCat;
public Job(int pJobnumber)
{
public Job(int pJobnumber) {
this.jobnumber = pJobnumber;
positionenQuantity = new ArrayList<Integer>();
positionenName = new ArrayList<String>();
@@ -28,32 +28,26 @@ public class Job
}
public void setJobtime(String jobtime)
{
public void setJobtime(String jobtime) {
this.jobtime = jobtime;
}
public int getJobnumber()
{
public int getJobnumber() {
return this.jobnumber;
}
public String getJobtime()
{
public String getJobtime() {
return this.jobtime;
}
public float getJobValue()
{
public float getJobValue() {
calcJobValue();
return this.jobvalue;
}
public void addPosition(String pPositionenName, float pPositionenValue,
String pPositionenCat)
{
public void addPosition(String pPositionenName, float pPositionenValue, String pPositionenCat) {
for (int i = 0; i < positionenName.size(); i++) {
if (positionenName.get(i).equals(pPositionenName)) {
// Item is already in list, increase quantity
@@ -70,49 +64,44 @@ public class Job
calcJobValue();
}
public void printJobOnConsole()
{
public void printJobOnConsole() {
System.out.println("---------------------------------------------");
System.out.println("JobNummer: " + jobnumber);
System.out.println("---------------------------------------------");
for (int i = 0; i < positionenName.size(); i++) {
System.out.println(
positionenQuantity.get(i) + " " + positionenName.get(i) + " "
+ positionenValue.get(i) + " " + positionenCat.get(i));
System.out.println(positionenQuantity.get(i) + " " + positionenName.get(i) + " " + positionenValue.get(i)
+ " " + positionenCat.get(i));
}
System.out.println("---------------------------------------------");
}
public ArrayList<tableDataCurrentOrder> getCurrentJobPositionen()
{
public ArrayList<tableDataCurrentOrder> getCurrentJobPositionen() {
ArrayList<tableDataCurrentOrder> jobitems = new ArrayList<tableDataCurrentOrder>();
for (int i = 0; i < positionenName.size(); i++) {
tableDataCurrentOrder tmp = new tableDataCurrentOrder(
positionenName.get(i), positionenQuantity.get(i));
tableDataCurrentOrder tmp = new tableDataCurrentOrder(positionenName.get(i), positionenQuantity.get(i));
jobitems.add(tmp);
}
return jobitems;
}
private void calcJobValue()
{
private void calcJobValue() {
jobvalue = 0;
for (int i = 0; i < positionenValue.size(); i++) {
jobvalue = jobvalue
+ (positionenQuantity.get(i) * positionenValue.get(i));
jobvalue = jobvalue + (positionenQuantity.get(i) * positionenValue.get(i));
}
// Round to two decimal places
jobvalue = BigDecimal.valueOf(jobvalue).setScale(2, RoundingMode.HALF_UP).floatValue();
}
public String createPosQuantityDBString()
{
public String createPosQuantityDBString() {
String tmp = String.valueOf(positionenQuantity.get(0));
for (int i = 1; i < positionenName.size(); i++) {
@@ -121,8 +110,7 @@ public class Job
return tmp;
}
public String createPosNameDBString()
{
public String createPosNameDBString() {
String tmp = positionenName.get(0);
for (int i = 1; i < positionenName.size(); i++) {
@@ -131,8 +119,7 @@ public class Job
return tmp;
}
public String createPosValueDBString()
{
public String createPosValueDBString() {
String tmp = String.valueOf(positionenValue.get(0));
for (int i = 1; i < positionenName.size(); i++) {
@@ -141,8 +128,7 @@ public class Job
return tmp;
}
public String createPosCatDBString()
{
public String createPosCatDBString() {
String tmp = positionenCat.get(0);
for (int i = 1; i < positionenName.size(); i++) {
@@ -151,8 +137,7 @@ public class Job
return tmp;
}
public void deletePosName(String pPosName)
{
public void deletePosName(String pPosName) {
for (int i = 0; i < positionenName.size(); i++) {
@@ -160,7 +145,8 @@ public class Job
if (positionenQuantity.get(i) > 1) {
positionenQuantity.set(i, positionenQuantity.get(i) - 1);
} else {
}
else {
positionenQuantity.remove(i);
positionenName.remove(i);
@@ -175,8 +161,15 @@ public class Job
}
public boolean existsPosName(String pPosName)
{
public void clearAllPositions() {
positionenQuantity.clear();
positionenName.clear();
positionenValue.clear();
positionenCat.clear();
jobvalue = 0;
}
public boolean existsPosName(String pPosName) {
for (int i = 0; i < positionenName.size(); i++) {
if (positionenName.get(i).equals(pPosName)) {
return true;
@@ -1,7 +1,6 @@
package com.jFxKasse.datatypes;
public abstract class PrintData
{
public abstract class PrintData {
protected int headerSpace;
@@ -30,7 +29,8 @@ public abstract class PrintData
protected String jobValue;
/**
* Constructor with all data that is not in the DB
* Constructor with all data that is not stored in the DB
*
* @param lineBreak
* @param headerSpace
* @param footerSpace
@@ -38,9 +38,8 @@ public abstract class PrintData
* @param header
* @param footer
*/
public PrintData(int lineBreak, int headerSpace, int footerSpace,
String timeAndDatePrint, String header, String footer)
{
public PrintData(int lineBreak, int headerSpace, int footerSpace, String timeAndDatePrint, String header,
String footer) {
this.lineBreak = lineBreak;
this.headerSpace = headerSpace;
this.footerSpace = footerSpace;
@@ -50,7 +49,8 @@ public abstract class PrintData
}
/**
* set all Data that is in the DB
* Set all data that is stored in the DB
*
* @param jobID
* @param timeAndDateOrder
* @param positionenQuantity
@@ -59,10 +59,8 @@ public abstract class PrintData
* @param positionenCategory
* @param jobValue
*/
public void setData(String jobID, String timeAndDateOrder,
String positionsQuantity, String positionsName,
String positionsValue, String positionsCategory, String jobValue)
{
public void setData(String jobID, String timeAndDateOrder, String positionsQuantity, String positionsName,
String positionsValue, String positionsCategory, String jobValue) {
this.jobID = jobID;
this.timeAndDateOrder = timeAndDateOrder;
this.positionsQuantity = positionsQuantity;
@@ -73,25 +71,26 @@ public abstract class PrintData
}
/**
* Breaks a string with newlines after the max line length
*
* @param data String
* @return same String splitted with \n after the max. line lenght
* @return the same String split with \n after max line length
*/
protected String breakLines(String data)
{
protected String breakLines(String data) {
boolean next = false;
int count = lineBreak;
if (data.length() > lineBreak) {
// Needs to be splitted
// Needs to be split
next = true;
} else {
// No need to be splitted
}
else {
// No need to split
return data;
}
// first part
// First part
String tmp = data.substring(0, lineBreak);
while (next) {
@@ -99,21 +98,22 @@ public abstract class PrintData
try {
tmp = tmp + "\n" + data.substring(count, lineBreak + count);
count = count + lineBreak;
} catch (Exception e) {
// data string not long enough
}
catch (Exception e) {
// Data string not long enough
next = false;
}
}
// add the last part
// Add the last part
return tmp + "\n" + data.substring(count);
}
/**
* prints a line of '--------'
* @return
* Prints a line of dashes '--------'
*
* @return separator line
*/
protected String getSeparator()
{
protected String getSeparator() {
String tmp = "-";
for (int i = 1; i < lineBreak; i++) {
@@ -123,12 +123,12 @@ public abstract class PrintData
}
/**
* sets a String into the center
* Centers a string within the line width
*
* @param data
* @return the centered String
* @return the centered string
*/
protected String setCenter(String data)
{
protected String setCenter(String data) {
int dataLenght = data.length();
int prefix = ((lineBreak - dataLenght) / 2);
String tmp = " ";
@@ -137,18 +137,18 @@ public abstract class PrintData
tmp = tmp + " ";
}
tmp = tmp + data;
return breakLines(tmp);
}
/**
* sets a String right-justified after an prefix
* Right-justifies a string after a prefix
*
* @param prefix
* @param data
* @return the right-justified String
* @return the right-justified string
*/
protected String setRight(String prefix, String data)
{
protected String setRight(String prefix, String data) {
int prefixLenght = prefix.length();
@@ -173,7 +173,7 @@ public abstract class PrintData
}
/**
* How the print String or Strings are made
* Defines how the print string(s) are generated
*/
abstract protected void generatePrintString();
@@ -1,12 +1,12 @@
package com.jFxKasse.datatypes;
public class PrintDataSimple extends PrintData
{
public class PrintDataSimple extends PrintData {
private String printString;
/**
* Constructor with all data that is not in the DB
* Constructor with all data that is not stored in the DB
*
* @param lineBreak
* @param headerSpace
* @param footerSpace
@@ -14,28 +14,25 @@ public class PrintDataSimple extends PrintData
* @param header
* @param footer
*/
public PrintDataSimple(int lineBreak, int headerSpace, int footerSpace,
String timeAndDatePrint, String header, String footer)
{
super(lineBreak, headerSpace, footerSpace, timeAndDatePrint, header,
footer);
public PrintDataSimple(int lineBreak, int headerSpace, int footerSpace, String timeAndDatePrint, String header,
String footer) {
super(lineBreak, headerSpace, footerSpace, timeAndDatePrint, header, footer);
}
/**
* Generates the String
* @return the final Print String
* Generates the print string
*
* @return the final print string
*/
public String getPrintString()
{
public String getPrintString() {
generatePrintString();
return this.printString;
}
@Override
protected void generatePrintString()
{
protected void generatePrintString() {
/* Header */
String header = "\n";
String header = " ";
for (int i = 1; i < headerSpace; i++) {
header = header + "\n";
}
@@ -45,16 +42,14 @@ public class PrintDataSimple extends PrintData
/* Info */
String info = setRight("Bestellung: ", timeAndDateOrder) + "\n"
+ setRight("Druck: ", timeAndDatePrint) + "\n"
String info = setRight("Bestellung: ", timeAndDateOrder) + "\n" + setRight("Druck: ", timeAndDatePrint) + "\n"
+ setRight("Bestellnummer: ", jobID);
/* Positionen */
String positionen = "\n";
int posCount = positionsQuantity.length()
- positionsQuantity.replace(";", "").length() + 1;
int posCount = positionsQuantity.length() - positionsQuantity.replace(";", "").length() + 1;
String[] positionQuantity = positionsQuantity.split(";");
@@ -65,8 +60,7 @@ public class PrintDataSimple extends PrintData
for (int i = 0; i < posCount; i++) { // All different posNames
int quantity = Integer.parseInt(positionQuantity[i]);
for (int j = 0; j < quantity; j++) { // quantities
positionen = positionen + setRight(breakLines(positionName[i]),
positionValue[i] + "") + "\n";
positionen = positionen + setRight(breakLines(positionName[i]), positionValue[i] + "") + "\n";
}
}
@@ -85,9 +79,8 @@ public class PrintDataSimple extends PrintData
/* Build final Print String */
printString = header + "\n" + getSeparator() + "\n" + info + "\n"
+ getSeparator() + "\n" + positionen + "\n" + getSeparator() + "\n"
+ price + "\n" + getSeparator() + "\n" + footer;
printString = header + "\n" + getSeparator() + "\n" + info + "\n" + getSeparator() + "\n" + positionen + "\n"
+ getSeparator() + "\n" + price + "\n" + getSeparator() + "\n" + footer;
}
}
@@ -2,8 +2,7 @@ package com.jFxKasse.datatypes;
import java.util.ArrayList;
public class PrintDataSplitted extends PrintData
{
public class PrintDataSplitted extends PrintData {
private ArrayList<String> printString = new ArrayList<String>();
private ArrayList<Category> categories = new ArrayList<Category>();
@@ -11,7 +10,8 @@ public class PrintDataSplitted extends PrintData
private int categoryCount = 0;
/**
* Constructor with all data that is not in the DB
* Constructor with all data that is not stored in the DB
*
* @param lineBreak
* @param headerSpace
* @param footerSpace
@@ -19,31 +19,28 @@ public class PrintDataSplitted extends PrintData
* @param header
* @param footer
*/
public PrintDataSplitted(int lineBreak, int headerSpace, int footerSpace,
String timeAndDatePrint, String header, String footer)
{
super(lineBreak, headerSpace, footerSpace, timeAndDatePrint, header,
footer);
public PrintDataSplitted(int lineBreak, int headerSpace, int footerSpace, String timeAndDatePrint, String header,
String footer) {
super(lineBreak, headerSpace, footerSpace, timeAndDatePrint, header, footer);
}
/**
* Generates the String
* @return the final Array with the Print Strings
* Generates the print strings
*
* @return the final array of print strings
*/
public ArrayList<String> getPrintStrings()
{
public ArrayList<String> getPrintStrings() {
generatePrintString();
return printString;
}
@Override
protected void generatePrintString()
{
protected void generatePrintString() {
String firstBill;
/* Header */
String header = "\n";
String header = "-";
for (int i = 1; i < headerSpace; i++) {
header = header + "\n";
}
@@ -53,11 +50,10 @@ public class PrintDataSplitted extends PrintData
/* Info */
String info = setRight("Bestellung: ", timeAndDateOrder) + "\n"
+ setRight("Druck: ", timeAndDatePrint) + "\n"
String info = setRight("Bestellung: ", timeAndDateOrder) + "\n" + setRight("Druck: ", timeAndDatePrint) + "\n"
+ setRight("Bestellnummer: ", jobID);
/* Splitted Bills */
/* Splitted Bills by Category */
/* Price */
@@ -74,21 +70,19 @@ public class PrintDataSplitted extends PrintData
/* Build first Print String */
firstBill = header + "\n" + getSeparator() + "\n" + info + "\n"
+ getSeparator() + "\n" + setCenter("Bon wurde aufgeteilt") + "\n"
+ getSeparator() + "\n" + price + "\n" + getSeparator() + "\n"
+ footer;
firstBill = header + "\n" + getSeparator() + "\n" + info + "\n" + getSeparator() + "\n"
+ setCenter("Bon wurde aufgeteilt") + "\n" + getSeparator() + "\n" + price + "\n" + getSeparator()
+ "\n" + footer;
printString.add(firstBill);
/* first bill ends here */
/* First bill ends here */
/* Categories in extra bills */
/* Category-specific extra bills */
String positions = null;
int posCount = positionsQuantity.length()
- positionsQuantity.replace(";", "").length() + 1;
int posCount = positionsQuantity.length() - positionsQuantity.replace(";", "").length() + 1;
String[] positionQuantity = positionsQuantity.split(";");
String[] positionName = positionsName.split(";");
@@ -97,17 +91,16 @@ public class PrintDataSplitted extends PrintData
for (int i = 0; i < posCount; i++) { // All different posNames
int quantity = Integer.parseInt(positionQuantity[i]);
insertToCategory(quantity, positionName[i], positionValue[i],
positionCategory[i]);
insertToCategory(quantity, positionName[i], positionValue[i], positionCategory[i]);
}
// loops through all categories
// Loops through all categories
for (int i = 0; i < categories.size(); i++) {
String thisBill;
/* Header */
header = "\n";
header = " ";
for (int o = 1; o < headerSpace; o++) {
header = header + "\n";
}
@@ -117,8 +110,7 @@ public class PrintDataSplitted extends PrintData
/* Info */
info = setRight("Bestellung: ", timeAndDateOrder) + "\n"
+ setRight("Druck: ", timeAndDatePrint) + "\n"
info = setRight("Bestellung: ", timeAndDateOrder) + "\n" + setRight("Druck: ", timeAndDatePrint) + "\n"
+ setRight("Bestellnummer: ", jobID);
/* Positions */
@@ -134,11 +126,9 @@ public class PrintDataSplitted extends PrintData
}
footer = footer + "_";
thisBill = header + "\n" + getSeparator() + "\n" + info + "\n"
+ getSeparator() + "\n"
+ setCenter(categories.get(i).getCategoryName()) + "\n"
+ getSeparator() + positions + "\n" + getSeparator() + "\n"
+ footer;
thisBill = header + "\n" + getSeparator() + "\n" + info + "\n" + getSeparator() + "\n"
+ setCenter(categories.get(i).getCategoryName()) + "\n" + getSeparator() + positions + "\n"
+ getSeparator() + "\n" + footer;
printString.add(thisBill);
@@ -146,9 +136,7 @@ public class PrintDataSplitted extends PrintData
}
private void insertToCategory(int quantity, String name, String value,
String category)
{
private void insertToCategory(int quantity, String name, String value, String category) {
boolean createNewCategorie = true;
for (int i = 0; i < categoryCount; i++) {
if (category.equals(categories.get(i).getCategoryName())) {
@@ -158,7 +146,7 @@ public class PrintDataSplitted extends PrintData
}
if (createNewCategorie) {
// position has a new category
// Position has a new category
categories.add(new Category(category));
categories.get(categoryCount).addPosition(quantity, name, value, this);
categoryCount++;
@@ -5,46 +5,38 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class tableDataCurrentOrder
{
public class tableDataCurrentOrder {
private final StringProperty position = new SimpleStringProperty();
private final IntegerProperty quantity = new SimpleIntegerProperty();
public tableDataCurrentOrder(final String pPosition, final Integer pQuantity)
{
public tableDataCurrentOrder(final String pPosition, final Integer pQuantity) {
this.position.set(pPosition);
this.quantity.set(pQuantity);
}
public StringProperty positionProperty()
{
public StringProperty positionProperty() {
return position;
}
public IntegerProperty quantityProperty()
{
public IntegerProperty quantityProperty() {
return quantity;
}
public String getPosition()
{
public String getPosition() {
return positionProperty().get();
}
public Integer getQuantity()
{
public Integer getQuantity() {
return quantityProperty().get();
}
public final void setPosition(String pPosition)
{
public final void setPosition(String pPosition) {
positionProperty().set(pPosition);
}
public final void setQuantity(int pQuantity)
{
public final void setQuantity(int pQuantity) {
quantityProperty().set(pQuantity);
}
}
@@ -5,8 +5,7 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class tableDataJob
{
public class tableDataJob {
private final IntegerProperty number = new SimpleIntegerProperty();
@@ -18,9 +17,8 @@ public class tableDataJob
private final StringProperty value = new SimpleStringProperty();
public tableDataJob(final Integer pNumber, final String pTime,
final String pPositionen, final String pStatus, final String pValue)
{
public tableDataJob(final Integer pNumber, final String pTime, final String pPositionen, final String pStatus,
final String pValue) {
this.number.set(pNumber);
this.time.set(pTime);
this.positionen.set(pPositionen);
@@ -29,78 +27,63 @@ public class tableDataJob
}
public IntegerProperty numberProperty()
{
public IntegerProperty numberProperty() {
return number;
}
public StringProperty timeProperty()
{
public StringProperty timeProperty() {
return time;
}
public StringProperty positionProperty()
{
public StringProperty positionProperty() {
return positionen;
}
public StringProperty statusProperty()
{
public StringProperty statusProperty() {
return status;
}
public StringProperty valueProperty()
{
public StringProperty valueProperty() {
return value;
}
public Integer getNumber()
{
public Integer getNumber() {
return numberProperty().get();
}
public String getTime()
{
public String getTime() {
return timeProperty().get();
}
public String getPosition()
{
public String getPosition() {
return positionProperty().get();
}
public String getStatus()
{
public String getStatus() {
return statusProperty().get();
}
public String getValue()
{
public String getValue() {
return valueProperty().get();
}
public final void setNumber(int pNumber)
{
public final void setNumber(int pNumber) {
numberProperty().set(pNumber);
}
public final void setTime(String pTime)
{
public final void setTime(String pTime) {
timeProperty().set(pTime);
}
public final void setPosition(String pPosition)
{
public final void setPosition(String pPosition) {
positionProperty().set(pPosition);
}
public final void setStatus(String pStatus)
{
public final void setStatus(String pStatus) {
statusProperty().set(pStatus);
}
public final void setValue(String pValue)
{
public final void setValue(String pValue) {
valueProperty().set(pValue);
}
@@ -5,21 +5,20 @@ import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class tableDataPositionen
{ // data-object with id, name, value, color
public class tableDataPositionen { // Data object with id, name, value, color
private final IntegerProperty id = new SimpleIntegerProperty();
private final StringProperty name = new SimpleStringProperty();
private final StringProperty value = new SimpleStringProperty();
private final StringProperty cat = new SimpleStringProperty();
private final StringProperty color = new SimpleStringProperty();
public tableDataPositionen(final int id, final String name, final String value, final String cat, final String color)
{
public tableDataPositionen(final int id, final String name, final String value, final String cat,
final String color) {
this.id.set(id);
this.name.set(name);
this.value.set(value);
@@ -27,75 +26,63 @@ public class tableDataPositionen
this.color.set(color);
}
public IntegerProperty idProperty()
{
public IntegerProperty idProperty() {
return id;
}
public StringProperty nameProperty()
{
public StringProperty nameProperty() {
return name;
}
public StringProperty valueProperty()
{
public StringProperty valueProperty() {
return value;
}
public StringProperty catProperty() {
return cat;
}
public StringProperty colorProperty()
{
public StringProperty colorProperty() {
return color;
}
public int getID()
{
public int getID() {
return idProperty().get();
}
public String getName()
{
public String getName() {
return nameProperty().get();
}
public String getValue()
{
public String getValue() {
return valueProperty().get();
}
public String getCat() {
return catProperty().get();
}
public String getColor()
{
public String getColor() {
return colorProperty().get();
}
public final void setID(int id)
{
public final void setID(int id) {
idProperty().set(id);
}
public final void setName(String name)
{
public final void setName(String name) {
nameProperty().set(name);
}
public final void setValue(String value)
{
public final void setValue(String value) {
valueProperty().set(value);
}
public final void setCat(String cat) {
catProperty().set(cat);
}
public final void setColor(String color)
{
public final void setColor(String color) {
colorProperty().set(color);
}
}
+1 -1
View File
@@ -1 +1 @@
/* JavaFX CSS - Leave this comment until you have at least create one rule which uses -fx-Property */
/* JavaFX CSS - Leave this comment until you have at least one rule which uses -fx-Property */
File diff suppressed because it is too large Load Diff
+677
View File
@@ -0,0 +1,677 @@
#!/usr/bin/env python3
"""
generate_report.py Erstellt einen deutschen HTML-Bericht aus einer SQLite-Datenbank
(Schema: category, positionen, jobs) erstellt mit jFxKasse.
Usage:
python generate_report.py <database.db> [-o output.html]
"""
import argparse
import json
import shutil
import sqlite3
import subprocess
import sys
from collections import defaultdict
from datetime import datetime, timedelta
from pathlib import Path
SOFTWARE_NAME = "jFxKasse"
SOFTWARE_URL = "https://git.mosad.xyz/localhorst/jFxKasse"
# ---------------------------------------------------------------------------
# Data helpers
# ---------------------------------------------------------------------------
def load_db(path: str) -> sqlite3.Connection:
con = sqlite3.connect(path)
con.row_factory = sqlite3.Row
return con
def parse_time(t: str) -> datetime:
return datetime.strptime(t.strip(), "%H:%M %d.%m.%Y")
def fmt_dt(dt: datetime) -> str:
return dt.strftime("%d.%m.%Y %H:%M Uhr")
def fmt_dur(td: timedelta) -> str:
total = int(td.total_seconds())
h, rem = divmod(total, 3600)
m = rem // 60
return f"{h} Std. {m} Min."
def fmt_eur(val) -> str:
try:
return f"{float(val):.2f}"
except Exception:
return str(val)
# ---------------------------------------------------------------------------
# Chart helpers
# ---------------------------------------------------------------------------
CHARTJS = "https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"
_chart_id = 0
def next_id(prefix="chart") -> str:
global _chart_id
_chart_id += 1
return f"{prefix}_{_chart_id}"
PALETTE = [
"#c0392b",
"#2980b9",
"#27ae60",
"#8e44ad",
"#d35400",
"#16a085",
"#f39c12",
"#2c3e50",
"#7f8c8d",
"#e74c3c",
"#1abc9c",
"#9b59b6",
"#e67e22",
"#34495e",
"#95a5a6",
]
def bar_chart_html(
labels: list,
values: list,
xlabel: str = "",
ylabel: str = "",
color: str = "#2c3e50",
height: int = 280,
) -> str:
cid = next_id("bar")
data = {
"labels": labels,
"datasets": [
{
"label": ylabel,
"data": values,
"backgroundColor": color,
"borderRadius": 4,
"borderSkipped": False,
}
],
}
opts = {
"responsive": True,
"maintainAspectRatio": False,
"plugins": {"legend": {"display": False}},
"scales": {
"x": {
"title": {
"display": bool(xlabel),
"text": xlabel,
"font": {"size": 11},
"color": "#555",
},
"grid": {"color": "#ebebeb"},
"ticks": {"color": "#555", "font": {"size": 10}},
},
"y": {
"title": {
"display": bool(ylabel),
"text": ylabel,
"font": {"size": 11},
"color": "#555",
},
"grid": {"color": "#ebebeb"},
"ticks": {"color": "#555", "font": {"size": 10}},
"beginAtZero": True,
},
},
}
return (
f'<div style="position:relative;height:{height}px;width:100%;margin:8px 0;">'
f'<canvas id="{cid}"></canvas></div>'
f"<script>(function(){{"
f'var ctx=document.getElementById("{cid}").getContext("2d");'
f'new Chart(ctx,{{type:"bar",'
f"data:{json.dumps(data, ensure_ascii=False)},"
f"options:{json.dumps(opts, ensure_ascii=False)}}});"
f"}})();</script>"
)
def pie_chart_html(labels: list, values: list, height: int = 280) -> str:
cid = next_id("pie")
colors = [PALETTE[i % len(PALETTE)] for i in range(len(labels))]
data = {
"labels": labels,
"datasets": [
{
"data": values,
"backgroundColor": colors,
"borderWidth": 2,
"borderColor": "#fff",
}
],
}
opts = {
"responsive": True,
"maintainAspectRatio": False,
"plugins": {
"legend": {
"position": "right",
"labels": {
"font": {
"size": 11,
"family": "'Source Code Pro','Courier New',monospace",
},
"color": "#2c2c2c",
"padding": 14,
"boxWidth": 14,
},
}
},
}
return (
f'<div style="position:relative;height:{height}px;width:100%;margin:8px 0;">'
f'<canvas id="{cid}"></canvas></div>'
f"<script>(function(){{"
f'var ctx=document.getElementById("{cid}").getContext("2d");'
f'new Chart(ctx,{{type:"pie",'
f"data:{json.dumps(data, ensure_ascii=False)},"
f"options:{json.dumps(opts, ensure_ascii=False)}}});"
f"}})();</script>"
)
# ---------------------------------------------------------------------------
# Analysis
# ---------------------------------------------------------------------------
def analyse(con: sqlite3.Connection) -> dict:
result = {}
result["categories"] = [
dict(r)
for r in con.execute("SELECT catid, catname FROM category ORDER BY catid")
]
result["positionen"] = [
dict(r)
for r in con.execute(
"SELECT p.posid, p.name, p.value, c.catname, p.color "
"FROM positionen p LEFT JOIN category c ON p.cat=c.catid "
"ORDER BY p.posid"
)
]
all_jobs = [dict(r) for r in con.execute("SELECT * FROM jobs ORDER BY jobid")]
times = [parse_time(j["time"]) for j in all_jobs]
result["first_job"] = min(times)
result["last_job"] = max(times)
result["duration"] = result["last_job"] - result["first_job"]
result["total_jobs"] = len(all_jobs)
result["storniert_count"] = sum(1 for j in all_jobs if j["state"] == "storniert")
result["verbucht_count"] = sum(1 for j in all_jobs if j["state"] == "verbucht")
vj = [j for j in all_jobs if j["state"] == "verbucht"]
values = [float(j["jobvalue"]) for j in vj]
result["sum_total"] = sum(values)
result["avg_value"] = sum(values) / len(values) if values else 0
min_val = min(values)
max_val = max(values)
result["min_job"] = next(j for j in vj if float(j["jobvalue"]) == min_val)
result["max_job"] = [j for j in vj if float(j["jobvalue"]) == max_val][-1]
# Overall hourly trend (verbucht, skip empty hours)
hour_counts: dict[str, int] = defaultdict(int)
for j in vj:
dt = parse_time(j["time"])
hour_counts[dt.strftime("%d.%m. %H:00")] += 1
sorted_hours = sorted(
hour_counts, key=lambda k: datetime.strptime(k, "%d.%m. %H:00")
)
result["trend_labels"] = sorted_hours
result["trend_values"] = [hour_counts[k] for k in sorted_hours]
# Per-category aggregations (expand semicolon-separated fields)
cat_units: dict[str, int] = defaultdict(int)
cat_revenue: dict[str, float] = defaultdict(float)
cat_pos_units: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
cat_pos_revenue: dict[str, dict[str, float]] = defaultdict(
lambda: defaultdict(float)
)
cat_day_hour: dict[str, dict[str, dict[int, int]]] = defaultdict(
lambda: defaultdict(lambda: defaultdict(int))
)
for j in vj:
dt = parse_time(j["time"])
day = dt.strftime("%d.%m.%Y")
qtys = j["positionen_quantity"].split(";")
names = j["positionen_name"].split(";")
vals = j["positionen_value"].split(";")
cats = j["positionen_cat"].split(";")
for qty, name, val, cat in zip(qtys, names, vals, cats):
cat = cat.strip()
name = name.strip()
try:
q = int(qty.strip())
except ValueError:
q = 1
try:
v = float(val.strip())
except ValueError:
v = 0.0
cat_units[cat] += q
cat_revenue[cat] += v * q
cat_pos_units[cat][name] += q
cat_pos_revenue[cat][name] += v * q
cat_day_hour[cat][day][dt.hour] += q
result["cat_units"] = dict(cat_units)
result["cat_revenue"] = dict(cat_revenue)
result["cat_pos_units"] = {c: dict(p) for c, p in cat_pos_units.items()}
result["cat_pos_revenue"] = {c: dict(p) for c, p in cat_pos_revenue.items()}
result["cat_day_hour"] = {
cat: {day: dict(hours) for day, hours in days.items()}
for cat, days in cat_day_hour.items()
}
return result
# ---------------------------------------------------------------------------
# CSS
# ---------------------------------------------------------------------------
CSS = """
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Source+Code+Pro:wght@400;600&display=swap');
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
:root{
--ink:#1a1a1a;--paper:#f7f4ef;--rule:#d4c9b8;
--red:#c0392b;--muted:#6b6560;--accent:#2c3e50;--white:#fff;
}
body{font-family:'Source Code Pro','Courier New',monospace;background:var(--paper);
color:var(--ink);font-size:13px;line-height:1.7;}
.page-wrap{max-width:980px;margin:0 auto;padding:48px 40px;}
.report-header{border-top:4px solid var(--ink);border-bottom:2px solid var(--rule);
padding:28px 0 24px;margin-bottom:40px;}
.report-header h1{font-family:'Playfair Display',Georgia,serif;font-size:2.1rem;
letter-spacing:-0.02em;color:var(--ink);margin-bottom:4px;}
.report-header .sub{color:var(--muted);font-size:11px;letter-spacing:0.08em;text-transform:uppercase;}
.sw-link{display:inline-block;margin-top:10px;font-size:11px;color:var(--accent);
text-decoration:none;border-bottom:1px solid var(--accent);opacity:.8;}
.sw-link:hover{opacity:1;}
.meta-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
gap:8px 24px;margin-top:18px;padding-top:16px;border-top:1px solid var(--rule);}
.meta-item{display:flex;flex-direction:column;gap:2px;}
.meta-label{font-size:10px;text-transform:uppercase;letter-spacing:0.1em;color:var(--muted);}
.meta-value{font-weight:600;color:var(--ink);font-size:12px;}
.section{margin-bottom:48px;}
.section-title{font-family:'Playfair Display',Georgia,serif;font-size:1.25rem;
border-bottom:2px solid var(--ink);padding-bottom:6px;margin-bottom:20px;color:var(--accent);}
.section-sub{font-size:10px;text-transform:uppercase;letter-spacing:0.1em;
color:var(--muted);margin-bottom:14px;margin-top:-10px;}
table{width:100%;border-collapse:collapse;font-size:12px;}
th{background:var(--ink);color:var(--paper);text-align:left;padding:8px 12px;
font-weight:600;letter-spacing:0.05em;font-size:10px;text-transform:uppercase;}
td{padding:7px 12px;border-bottom:1px solid var(--rule);vertical-align:top;}
tr:nth-child(even) td{background:rgba(0,0,0,.025);}
.kpi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(155px,1fr));
gap:14px;margin-bottom:22px;}
.kpi-card{border:1px solid var(--rule);padding:15px 16px;background:var(--white);position:relative;}
.kpi-card::before{content:'';position:absolute;top:0;left:0;width:3px;height:100%;background:var(--red);}
.kpi-label{font-size:10px;text-transform:uppercase;letter-spacing:0.1em;color:var(--muted);margin-bottom:3px;}
.kpi-value{font-family:'Playfair Display',Georgia,serif;font-size:1.45rem;color:var(--ink);line-height:1.2;}
.kpi-sub{font-size:10px;color:var(--muted);margin-top:4px;}
.dot{display:inline-block;width:10px;height:10px;border-radius:50%;
margin-right:6px;vertical-align:middle;border:1px solid rgba(0,0,0,.15);}
.chart-grid-2{display:grid;grid-template-columns:repeat(auto-fit,minmax(420px,1fr));gap:24px;margin-top:8px;}
.chart-box{border:1px solid var(--rule);padding:18px 18px 14px;background:var(--white);}
.chart-box-title{font-size:11px;font-weight:600;text-transform:uppercase;
letter-spacing:0.08em;color:var(--accent);margin-bottom:3px;}
.chart-box-note{font-size:11px;color:var(--muted);margin-bottom:8px;}
.rule{border:none;border-top:1px solid var(--rule);margin:32px 0;}
.report-footer{border-top:2px solid var(--ink);padding-top:14px;margin-top:48px;
font-size:10px;color:var(--muted);display:flex;justify-content:space-between;flex-wrap:wrap;gap:4px;}
.report-footer a{color:inherit;}
@media print{
body{background:#fff;}
.page-wrap{padding:20px;}
.chart-grid-2{grid-template-columns:1fr 1fr;}
.section,.kpi-grid,.chart-box{page-break-inside:avoid;}
}
"""
# ---------------------------------------------------------------------------
# HTML builder
# ---------------------------------------------------------------------------
def build_html(db_path: str, data: dict, export_time: datetime) -> str:
p = Path(db_path)
display_name = p.name
file_size_kb = p.stat().st_size / 1024
file_mtime = datetime.fromtimestamp(p.stat().st_mtime)
# ---- Header ----
header = f"""
<div class="report-header">
<div class="sub">Auswertungsbericht · Kassensystem</div>
<h1>{display_name}</h1>
<a class="sw-link" href="{SOFTWARE_URL}" target="_blank">Erstellt mit {SOFTWARE_NAME}</a>
<div class="meta-grid">
<div class="meta-item"><span class="meta-label">Dateiname</span>
<span class="meta-value">{display_name}</span></div>
<div class="meta-item"><span class="meta-label">Dateigröße</span>
<span class="meta-value">{file_size_kb:.1f} KB</span></div>
<div class="meta-item"><span class="meta-label">Erstellt am</span>
<span class="meta-value">{file_mtime.strftime('%d.%m.%Y %H:%M Uhr')}</span></div>
<div class="meta-item"><span class="meta-label">Export um</span>
<span class="meta-value">{export_time.strftime('%d.%m.%Y %H:%M Uhr')}</span></div>
</div>
</div>"""
# ---- Kategorien ----
cat_rows = "".join(
f'<tr><td>{c["catid"]}</td><td>{c["catname"]}</td></tr>'
for c in data["categories"]
)
section_cat = f"""
<div class="section">
<div class="section-title">Kategorien</div>
<table>
<thead><tr><th>#</th><th>Kategoriename</th></tr></thead>
<tbody>{cat_rows}</tbody>
</table>
</div>"""
# ---- Positionen ----
pos_rows = "".join(
f'<tr><td>{pos["posid"]}</td>'
f'<td><span class="dot" style="background:{pos["color"]};"></span>{pos["name"]}</td>'
f'<td>{fmt_eur(pos["value"])}</td>'
f'<td>{pos["catname"] or ""}</td></tr>'
for pos in data["positionen"]
)
section_pos = f"""
<div class="section">
<div class="section-title">Positionen / Artikel</div>
<table>
<thead><tr><th>#</th><th>Bezeichnung</th><th>Preis</th><th>Kategorie</th></tr></thead>
<tbody>{pos_rows}</tbody>
</table>
</div>"""
# ---- Jobs ----
min_j = data["min_job"]
max_j = data["max_job"]
kpi_overview = f"""
<div class="kpi-grid">
<div class="kpi-card"><div class="kpi-label">Erster Auftrag</div>
<div class="kpi-value" style="font-size:.95rem;">{fmt_dt(data["first_job"])}</div></div>
<div class="kpi-card"><div class="kpi-label">Letzter Auftrag</div>
<div class="kpi-value" style="font-size:.95rem;">{fmt_dt(data["last_job"])}</div></div>
<div class="kpi-card"><div class="kpi-label">Betriebsdauer</div>
<div class="kpi-value" style="font-size:.95rem;">{fmt_dur(data["duration"])}</div></div>
<div class="kpi-card"><div class="kpi-label">Aufträge gesamt</div>
<div class="kpi-value">{data["total_jobs"]}</div></div>
<div class="kpi-card"><div class="kpi-label">Verbucht</div>
<div class="kpi-value" style="color:#27ae60;">{data["verbucht_count"]}</div></div>
<div class="kpi-card"><div class="kpi-label">Storniert</div>
<div class="kpi-value" style="color:#c0392b;">{data["storniert_count"]}</div></div>
</div>"""
kpi_deep = f"""
<div class="kpi-grid">
<div class="kpi-card"><div class="kpi-label">Gesamtumsatz</div>
<div class="kpi-value">{fmt_eur(data["sum_total"])}</div>
<div class="kpi-sub">nur verbuchte Aufträge</div></div>
<div class="kpi-card"><div class="kpi-label">Durchschnitt / Auftrag</div>
<div class="kpi-value">{fmt_eur(data["avg_value"])}</div>
<div class="kpi-sub">nur verbuchte Aufträge</div></div>
<div class="kpi-card"><div class="kpi-label">Kleinster Auftrag</div>
<div class="kpi-value">{fmt_eur(min_j["jobvalue"])}</div>
<div class="kpi-sub">Job #{min_j["jobid"]} · {min_j["time"]}</div></div>
<div class="kpi-card"><div class="kpi-label">Größter Auftrag</div>
<div class="kpi-value">{fmt_eur(max_j["jobvalue"])}</div>
<div class="kpi-sub">Job #{max_j["jobid"]} · {max_j["time"]}</div></div>
</div>"""
trend_chart = bar_chart_html(
data["trend_labels"],
data["trend_values"],
xlabel="Stunde",
ylabel="Anzahl Aufträge",
color="#2c3e50",
height=300,
)
section_jobs = f"""
<div class="section">
<div class="section-title">Auftragsübersicht</div>
<div class="section-sub">Alle Aufträge</div>
{kpi_overview}
<hr class="rule">
<div class="section-sub">Tiefenanalyse nur verbuchte Aufträge</div>
{kpi_deep}
<hr class="rule">
<div class="section-sub">Trendverlauf Aufträge pro Stunde (gesamte Datenbank)</div>
{trend_chart}
</div>"""
# ---- Pie 1: Einheiten je Kategorie ----
cu = data["cat_units"]
pie_units = pie_chart_html(
[f"{k} ({v} Stk.)" for k, v in cu.items()], list(cu.values()), height=300
)
note_units = " &nbsp;·&nbsp; ".join(
f"<strong>{k}</strong>: {v} Stk." for k, v in cu.items()
)
# ---- Pie 2: Umsatz je Kategorie ----
cr = data["cat_revenue"]
pie_revenue = pie_chart_html(
[f"{k} ({fmt_eur(v)})" for k, v in cr.items()], list(cr.values()), height=300
)
note_revenue = " &nbsp;·&nbsp; ".join(
f"<strong>{k}</strong>: {fmt_eur(v)}" for k, v in cr.items()
)
section_cat_pies = f"""
<div class="section">
<div class="section-title">Kategorienauswertung</div>
<div class="section-sub">Verkaufte Einheiten nach Kategorie (nur verbuchte Aufträge)</div>
<div class="chart-box" style="margin-bottom:28px;">
<div class="chart-box-title">Einheiten je Kategorie</div>
<div class="chart-box-note">{note_units}</div>
{pie_units}
</div>
<div class="section-sub">Umsatz nach Kategorie (nur verbuchte Aufträge)</div>
<div class="chart-box">
<div class="chart-box-title">Umsatz je Kategorie</div>
<div class="chart-box-note">{note_revenue}</div>
{pie_revenue}
</div>
</div>"""
# ---- Pie 3: Positionen je Kategorie ----
pos_pie_boxes = ""
for cat, pos_dict in sorted(data["cat_pos_units"].items()):
labels = list(pos_dict.keys())
values = list(pos_dict.values())
total = sum(values)
note = " &nbsp;·&nbsp; ".join(
f"<strong>{l}</strong>: {v}" for l, v in zip(labels, values)
)
pie = pie_chart_html(
[f"{l} ({v})" for l, v in zip(labels, values)], values, height=260
)
pos_pie_boxes += f"""
<div class="chart-box">
<div class="chart-box-title">{cat}</div>
<div class="chart-box-note">Gesamt: {total} Einheiten</div>
{pie}
<div class="chart-box-note" style="margin-top:10px;font-size:10px;line-height:1.8;">{note}</div>
</div>"""
section_pos_pies = f"""
<div class="section">
<div class="section-title">Positionen je Kategorie</div>
<div class="section-sub">Verkaufte Stückzahl pro Artikel, aufgeteilt nach Kategorie</div>
<div class="chart-grid-2">
{pos_pie_boxes}
</div>
</div>"""
# ---- Per-category trend per day ----
cat_names = sorted(data["cat_day_hour"].keys())
cat_trend_boxes = ""
for i, cat in enumerate(cat_names):
color = PALETTE[i % len(PALETTE)]
days = data["cat_day_hour"][cat]
for day in sorted(days, key=lambda d: datetime.strptime(d, "%d.%m.%Y")):
hours = days[day]
labels = [f"{h}:00" for h in sorted(hours)]
values = [hours[h] for h in sorted(hours)]
chart = bar_chart_html(
labels,
values,
xlabel="Stunde",
ylabel="Einheiten",
color=color,
height=220,
)
cat_trend_boxes += f"""
<div class="chart-box">
<div class="chart-box-title">{cat} · {day}</div>
{chart}
</div>"""
section_trends = f"""
<div class="section">
<div class="section-title">Trendverlauf pro Kategorie und Tag</div>
<div class="section-sub">Verkaufte Artikel-Einheiten pro Stunde</div>
<div class="chart-grid-2">
{cat_trend_boxes}
</div>
</div>"""
# ---- Footer ----
footer = f"""
<div class="report-footer">
<span>Exportiert am {export_time.strftime('%d.%m.%Y um %H:%M Uhr')}</span>
<span>Quelle: {display_name} &nbsp;·&nbsp;
<a href="{SOFTWARE_URL}">{SOFTWARE_NAME}</a></span>
</div>"""
return f"""<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Bericht {display_name}</title>
<script src="{CHARTJS}"></script>
<style>{CSS}</style>
</head>
<body>
<div class="page-wrap">
{header}
{section_cat}
<hr class="rule">
{section_pos}
<hr class="rule">
{section_jobs}
<hr class="rule">
{section_cat_pies}
<hr class="rule">
{section_pos_pies}
<hr class="rule">
{section_trends}
{footer}
</div>
</body>
</html>"""
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser(
prog="generate_report",
description="Erstellt einen deutschen HTML-Bericht aus einer jFxKasse SQLite-Datenbank.",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Beispiele:\n"
" python generate_report.py kassendaten.db\n"
" python generate_report.py kassendaten.db -o bericht.html\n"
),
)
p.add_argument("database", help="Pfad zur SQLite-Datenbankdatei")
p.add_argument(
"-o",
"--output",
metavar="OUTPUT_HTML",
help="Pfad zur Ausgabe-HTML-Datei (Standard: <dateiname>_bericht.html)",
)
return p
def main():
parser = build_parser()
args = parser.parse_args()
db_path = Path(args.database)
if not db_path.exists():
parser.error(f"Datei nicht gefunden: {db_path}")
out_path = (
Path(args.output)
if args.output
else db_path.with_name(db_path.stem + "_bericht.html")
)
print(f"[lade] {db_path.name}")
con = load_db(str(db_path))
print("[analyse] Daten werden ausgewertet …")
data = analyse(con)
con.close()
print("[render] HTML wird erstellt …")
html = build_html(str(db_path), data, datetime.now())
out_path.write_text(html, encoding="utf-8")
print(f"[fertig] HTML gespeichert: {out_path}")
if __name__ == "__main__":
main()
+281
View File
@@ -0,0 +1,281 @@
#!/usr/bin/env python3
"""
merge_db.py Merge multiple SQLite databases into one output file.
Rules
-----
* Tables `category` and `positionen` are deduplicated by ALL columns
(content-based identity, ignoring the local rowid/id column).
* Table `jobs` rows from later files are appended with a re-sequenced
`jobid` so IDs never clash across files.
* Rows are inserted in file order: the first file wins on deduplication
for reference tables; job rows from later files come after earlier ones.
Usage
-----
python merge_db.py -o merged.db 02.db 03.db [04.db ...]
"""
import argparse
import shutil
import sqlite3
import sys
from pathlib import Path
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def get_table_names(con: sqlite3.Connection) -> list[str]:
cur = con.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
return [row[0] for row in cur.fetchall()]
def get_columns(con: sqlite3.Connection, table: str) -> list[str]:
cur = con.execute(f'PRAGMA table_info("{table}")')
return [row[1] for row in cur.fetchall()]
def get_create_statement(con: sqlite3.Connection, table: str) -> str:
cur = con.execute(
"SELECT sql FROM sqlite_master WHERE type='table' AND name=?", (table,)
)
row = cur.fetchone()
return row[0] if row else None
# ---------------------------------------------------------------------------
# Merge logic per table type
# ---------------------------------------------------------------------------
def merge_dedup_table(
src: sqlite3.Connection,
dst: sqlite3.Connection,
table: str,
id_col: str,
) -> tuple[int, int]:
"""
Merge a reference table using content-based deduplication.
Rows are compared on every column *except* `id_col`. If an identical
row already exists in `dst`, it is skipped; otherwise it is inserted
with a new auto-incremented id.
Returns (rows_read, rows_inserted).
"""
cols = get_columns(src, table)
content_cols = [c for c in cols if c != id_col]
if not content_cols:
return 0, 0
placeholders = ", ".join("?" * len(content_cols))
select_content = ", ".join(f'"{c}"' for c in content_cols)
# Build a set of existing content tuples for fast dedup lookup
existing: set[tuple] = set()
for row in dst.execute(f'SELECT {select_content} FROM "{table}"'):
existing.add(row)
src_rows = src.execute(f'SELECT {select_content} FROM "{table}"').fetchall()
inserted = 0
for row in src_rows:
key = tuple(row)
if key not in existing:
dst.execute(
f'INSERT INTO "{table}" ({select_content}) VALUES ({placeholders})',
row,
)
existing.add(key)
inserted += 1
return len(src_rows), inserted
def merge_jobs_table(
src: sqlite3.Connection,
dst: sqlite3.Connection,
table: str = "jobs",
id_col: str = "jobid",
) -> tuple[int, int]:
"""
Append job rows from `src` into `dst`, re-sequencing `id_col` so that
new IDs start after the current maximum in `dst`.
Duplicate detection is done on all columns except `id_col`; rows with
identical content are skipped.
Returns (rows_read, rows_inserted).
"""
cols = get_columns(src, table)
content_cols = [c for c in cols if c != id_col]
all_col_str = ", ".join(f'"{c}"' for c in cols)
content_str = ", ".join(f'"{c}"' for c in content_cols)
placeholders = ", ".join("?" * len(cols))
# Current max id in destination
row = dst.execute(f'SELECT MAX("{id_col}") FROM "{table}"').fetchone()
offset = (row[0] or 0)
# Existing content fingerprints for dedup
existing: set[tuple] = set()
for row in dst.execute(f'SELECT {content_str} FROM "{table}"'):
existing.add(tuple(row))
src_rows = src.execute(f'SELECT {all_col_str} FROM "{table}"').fetchall()
col_index = {c: i for i, c in enumerate(cols)}
id_idx = col_index[id_col]
inserted = 0
for row in src_rows:
content_key = tuple(row[col_index[c]] for c in content_cols)
if content_key in existing:
continue # skip duplicate content
new_id = row[id_idx] + offset
new_row = list(row)
new_row[id_idx] = new_id
dst.execute(
f'INSERT INTO "{table}" ({all_col_str}) VALUES ({placeholders})',
new_row,
)
existing.add(content_key)
inserted += 1
return len(src_rows), inserted
# ---------------------------------------------------------------------------
# Schema helpers
# ---------------------------------------------------------------------------
def ensure_table(dst: sqlite3.Connection, src: sqlite3.Connection, table: str) -> None:
"""Create `table` in `dst` if it doesn't exist yet, using src's DDL."""
exists = dst.execute(
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (table,)
).fetchone()
if not exists:
ddl = get_create_statement(src, table)
if ddl:
dst.execute(ddl)
# ---------------------------------------------------------------------------
# Main merge routine
# ---------------------------------------------------------------------------
# Map table name → (id_column, merge_strategy)
# strategy: "dedup" → content-based dedup, new ids assigned
# "jobs" → append with id re-sequencing + content dedup
TABLE_CONFIG: dict[str, tuple[str, str]] = {
"category": ("catid", "dedup"),
"positionen": ("posid", "dedup"),
"jobs": ("jobid", "jobs"),
}
def merge_databases(input_files: list[Path], output_file: Path) -> None:
# Bootstrap: copy first file as the base for the output
print(f"[init] Bootstrapping output from '{input_files[0].name}'")
shutil.copy2(input_files[0], output_file)
dst = sqlite3.connect(output_file)
dst.execute("PRAGMA journal_mode=WAL")
dst.execute("PRAGMA foreign_keys=OFF")
try:
for src_path in input_files[1:]:
print(f"\n[merge] Processing '{src_path.name}'")
src = sqlite3.connect(src_path)
src_tables = get_table_names(src)
for table in src_tables:
ensure_table(dst, src, table)
config = TABLE_CONFIG.get(table)
if config is None:
# Unknown table: fall back to content-based dedup without
# special id handling treat first column as the id.
cols = get_columns(src, table)
id_col = cols[0] if cols else None
if id_col:
read, ins = merge_dedup_table(src, dst, table, id_col)
print(f" [{table}] (fallback dedup on '{id_col}') "
f"read={read} inserted={ins}")
else:
id_col, strategy = config
if strategy == "dedup":
read, ins = merge_dedup_table(src, dst, table, id_col)
else:
read, ins = merge_jobs_table(src, dst, table, id_col)
print(f" [{table}] strategy={strategy} "
f"read={read} inserted={ins}")
dst.commit()
src.close()
finally:
dst.close()
print(f"\n[done] Output written to '{output_file}'")
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="merge_db",
description=(
"Merge multiple SQLite databases into one output file.\n"
"Files are merged in the order they are provided.\n"
"Duplicate rows are detected by content and inserted only once."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" python merge_db.py -o merged.db 02.db 03.db\n"
" python merge_db.py -o merged.db 02.db 03.db 04.db 05.db\n"
),
)
parser.add_argument(
"inputs",
metavar="INPUT_DB",
nargs="+",
help="Input SQLite database files (in desired merge order)",
)
parser.add_argument(
"-o", "--output",
metavar="OUTPUT_DB",
required=True,
help="Path for the merged output database (will be overwritten if it exists)",
)
return parser
def main() -> None:
parser = build_parser()
args = parser.parse_args()
input_paths = [Path(p) for p in args.inputs]
output_path = Path(args.output)
# Validate inputs
for p in input_paths:
if not p.exists():
parser.error(f"Input file not found: {p}")
if not p.is_file():
parser.error(f"Not a file: {p}")
if len(input_paths) < 2:
parser.error("At least two input files are required.")
if output_path.exists():
print(f"[warn] Output file '{output_path}' already exists and will be overwritten.")
merge_databases(input_paths, output_path)
if __name__ == "__main__":
main()