Documentation Index
Fetch the complete documentation index at: https://mintlify.com/hxmz-axfn07/qr-printing-sfw/llms.txt
Use this file to discover all available pages before exploring further.
QR Print Station stores all orders, documents, pricing rules, and shop settings in a MySQL database. The database is the system of record for every print job: from the moment a customer submits an order through to completion, cancellation, or failure. This page explains how to create the database, what each table stores, and how the server keeps the schema up-to-date automatically as the software evolves.
Requirements
- MySQL 5.7+ or MariaDB 10.3+
- Connection handled by the PyMySQL 1.1.1 driver using the
utf8mb4 charset and utf8mb4_unicode_ci collation, which provides full Unicode support including emoji
All connection parameters are read from .env. The defaults match what schema.sql creates:
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MYSQL_USER=qr_print_user
MYSQL_PASSWORD=change_me
MYSQL_DATABASE=qr_printing
Initial Setup
Apply server/schema.sql to your MySQL server to create the database, user, and all four tables in a single step:
mysql -u root -p < server/schema.sql
The script executes the following actions in order:
- Creates the
qr_printing database with utf8mb4 charset if it does not already exist.
- Creates the
qr_print_user MySQL account with the password change_me if it does not already exist.
- Grants all privileges on the
qr_printing database to qr_print_user.
- Creates the
orders, documents, pricing, and settings tables.
- Seeds
pricing with four default A4 rates and settings with default shop metadata.
The default password for qr_print_user is change_me. Change it before running the server in any environment that is accessible beyond your development machine. Either edit the IDENTIFIED BY clause in schema.sql before running it, or update the account afterwards:ALTER USER 'qr_print_user'@'localhost' IDENTIFIED BY 'your-strong-password';
FLUSH PRIVILEGES;
Then update MYSQL_PASSWORD in .env to match.
Tables
orders
Every customer submission creates one row in orders. Status transitions move an order through a defined lifecycle managed by the admin dashboard.
| Column | Type | Description |
|---|
id | VARCHAR(36) | UUID v4 primary key, generated by the server |
customer_name | VARCHAR(255) | Required; defaults to "Walk-in customer" if the field is blank |
phone | VARCHAR(80) | Optional contact number |
notes | TEXT | Order-level notes entered by the customer at submission time |
status | ENUM | Lifecycle state: new, reviewing, ready, printing, printed, cancelled, failed |
total_estimated_cost | DECIMAL(10,2) | Sum of estimated_cost across all documents in the order |
cancel_reason | TEXT | Populated when a staff member cancels an active order |
failure_reason | TEXT | Populated when a print command exits with a non-zero return code |
deleted_at | DATETIME | Soft-delete marker; NULL means the order is active |
created_at | DATETIME | Set once on order creation |
updated_at | DATETIME | Updated on every status transition |
Status flow:
new → reviewing → ready → printing → printed
↘ cancelled (from any active status)
↘ failed (from printing only)
documents
Each file uploaded within an order is a separate documents row. One order can contain multiple documents, each with its own print settings and cost estimate.
| Column | Type | Description |
|---|
id | VARCHAR(36) | UUID v4 primary key |
order_id | VARCHAR(36) | Foreign key to orders.id; cascades on delete |
original_name | VARCHAR(255) | The filename as provided by the customer’s browser |
stored_name | VARCHAR(255) | Disk filename, formatted as <order_id>_<doc_id>.<ext> |
mime_type | VARCHAR(100) | Detected from the upload’s Content-Type header or inferred from the extension |
size_bytes | BIGINT | Raw file size in bytes |
page_count | INT | Number of pages detected by scanning for /Type /Page PDF objects; NULL for non-PDF files |
color_mode | ENUM | bw (black and white) or color |
print_style | ENUM | single (single-sided) or double (double-sided) |
paper_size | VARCHAR(30) | Customer-selected paper size, e.g. A4, A3, Letter |
copies | INT | Number of copies, clamped to the range 1–99 |
status | ENUM | uploaded, approved, printing, printed, failed |
notes | TEXT | Per-document notes entered by the customer |
estimated_cost | DECIMAL(10,2) | Calculated as price_per_page × page_count × copies using the matching pricing row; NULL if no pricing rule matches |
deleted_at | DATETIME | Soft-delete marker |
created_at | DATETIME | Set once when the document row is inserted |
pricing
Stores per-page prices for each combination of paper size, color mode, and print style. The table is seeded by schema.sql with four default A4 rates:
paper_size | color_mode | print_style | price_per_page |
|---|
| A4 | bw | single | 2.00 |
| A4 | bw | double | 1.50 |
| A4 | color | single | 10.00 |
| A4 | color | double | 8.00 |
The server reads all non-deleted pricing rows at order-creation time and uses them to calculate estimated_cost per document. You can manage pricing entries directly in the database or through the admin dashboard; the config.yml pricing section is used only as a seed source on first startup (rows are inserted with INSERT IGNORE).
| Column | Type | Description |
|---|
id | INT AUTO_INCREMENT | Integer primary key |
paper_size | VARCHAR(30) | e.g. A4, A3, Letter |
color_mode | ENUM | bw or color |
print_style | ENUM | single or double |
price_per_page | DECIMAL(10,2) | Cost per printed page |
deleted_at | DATETIME | Soft-delete marker |
A unique key on (paper_size, color_mode, print_style) prevents duplicate pricing rules.
settings
Key-value store for shop configuration that overrides config.yml values at runtime. The admin dashboard reads from and writes to this table, allowing shop name, address, phone, upload limit, and currency symbol to be changed without editing files.
| Column | Type | Description |
|---|
setting_key | VARCHAR(100) | Primary key; e.g. shop_name, shop_address, shop_phone, max_upload_mb, currency_symbol, theme_accent |
setting_value | TEXT | The string value for that setting |
Default values seeded by schema.sql:
setting_key | Default value |
|---|
shop_name | QR Print Station |
shop_address | Local print shop |
shop_phone | (empty) |
max_upload_mb | 50 |
currency_symbol | Rs |
Auto-Migration
On every startup, main() calls init_db(), which calls migrate_schema() before returning. The migration routine uses information_schema.COLUMNS to check whether each expected column exists in its table. If a column is missing, it runs the corresponding ALTER TABLE statement to add it. This means you can upgrade QR Print Station to a newer version and simply restart — no manual ALTER TABLE commands are needed and no existing data is lost.
Columns currently covered by auto-migration:
| Table | Column | Added SQL |
|---|
orders | total_estimated_cost | DECIMAL(10,2) DEFAULT 0.00 |
orders | deleted_at | DATETIME NULL |
documents | mime_type | VARCHAR(100) |
documents | status | ENUM('uploaded','approved','printing','printed','failed') |
documents | deleted_at | DATETIME NULL |
The migration step also normalises ENUM definitions and migrates any legacy completed status values in the orders table to printed.
File Storage
Uploaded files are written to the uploads/ directory in the repository root, not to the database. The disk filename follows the pattern <order_id>_<doc_id>.<ext>, which matches the stored_name column in the documents table. The admin dashboard serves these files through the /files/<doc_id> endpoint (admin-only).
uploads/
└── 550e8400-e29b-41d4-a716-446655440000_f47ac10b-58cc-4372-a567-0e02b2c3d479.pdf
The uploads/ directory and the MySQL database are independent stores that must both be backed up to preserve complete order history. A database dump alone is not sufficient — the uploaded files will be missing. Back them up together:# Back up the database
mysqldump -u qr_print_user -p qr_printing > qr_printing_backup.sql
# Back up uploaded files
tar -czf uploads_backup.tar.gz uploads/
Store both archives in the same location so they stay in sync.