Skip to main content

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:
  1. Creates the qr_printing database with utf8mb4 charset if it does not already exist.
  2. Creates the qr_print_user MySQL account with the password change_me if it does not already exist.
  3. Grants all privileges on the qr_printing database to qr_print_user.
  4. Creates the orders, documents, pricing, and settings tables.
  5. 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.
ColumnTypeDescription
idVARCHAR(36)UUID v4 primary key, generated by the server
customer_nameVARCHAR(255)Required; defaults to "Walk-in customer" if the field is blank
phoneVARCHAR(80)Optional contact number
notesTEXTOrder-level notes entered by the customer at submission time
statusENUMLifecycle state: new, reviewing, ready, printing, printed, cancelled, failed
total_estimated_costDECIMAL(10,2)Sum of estimated_cost across all documents in the order
cancel_reasonTEXTPopulated when a staff member cancels an active order
failure_reasonTEXTPopulated when a print command exits with a non-zero return code
deleted_atDATETIMESoft-delete marker; NULL means the order is active
created_atDATETIMESet once on order creation
updated_atDATETIMEUpdated 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.
ColumnTypeDescription
idVARCHAR(36)UUID v4 primary key
order_idVARCHAR(36)Foreign key to orders.id; cascades on delete
original_nameVARCHAR(255)The filename as provided by the customer’s browser
stored_nameVARCHAR(255)Disk filename, formatted as <order_id>_<doc_id>.<ext>
mime_typeVARCHAR(100)Detected from the upload’s Content-Type header or inferred from the extension
size_bytesBIGINTRaw file size in bytes
page_countINTNumber of pages detected by scanning for /Type /Page PDF objects; NULL for non-PDF files
color_modeENUMbw (black and white) or color
print_styleENUMsingle (single-sided) or double (double-sided)
paper_sizeVARCHAR(30)Customer-selected paper size, e.g. A4, A3, Letter
copiesINTNumber of copies, clamped to the range 1–99
statusENUMuploaded, approved, printing, printed, failed
notesTEXTPer-document notes entered by the customer
estimated_costDECIMAL(10,2)Calculated as price_per_page × page_count × copies using the matching pricing row; NULL if no pricing rule matches
deleted_atDATETIMESoft-delete marker
created_atDATETIMESet 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_sizecolor_modeprint_styleprice_per_page
A4bwsingle2.00
A4bwdouble1.50
A4colorsingle10.00
A4colordouble8.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).
ColumnTypeDescription
idINT AUTO_INCREMENTInteger primary key
paper_sizeVARCHAR(30)e.g. A4, A3, Letter
color_modeENUMbw or color
print_styleENUMsingle or double
price_per_pageDECIMAL(10,2)Cost per printed page
deleted_atDATETIMESoft-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.
ColumnTypeDescription
setting_keyVARCHAR(100)Primary key; e.g. shop_name, shop_address, shop_phone, max_upload_mb, currency_symbol, theme_accent
setting_valueTEXTThe string value for that setting
Default values seeded by schema.sql:
setting_keyDefault value
shop_nameQR Print Station
shop_addressLocal print shop
shop_phone(empty)
max_upload_mb50
currency_symbolRs

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:
TableColumnAdded SQL
orderstotal_estimated_costDECIMAL(10,2) DEFAULT 0.00
ordersdeleted_atDATETIME NULL
documentsmime_typeVARCHAR(100)
documentsstatusENUM('uploaded','approved','printing','printed','failed')
documentsdeleted_atDATETIME 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.

Build docs developers (and LLMs) love