modified: README.md

modified:   app.py
This commit is contained in:
2026-04-17 16:24:24 -04:00
parent 83f9f606de
commit caf73ce156
5 changed files with 145 additions and 6 deletions

View File

@@ -10,6 +10,44 @@ A reactive POS inventory system for software engineers with a snack addiction. F
- **Secure:** Hashed password authentication via Flask-Login.
- **On device scanner:** Add and scan products from within your phone!
## 📦 Building the Desktop App (.exe)
If you want to run SekiPOS as a standalone Windows application with its own embedded browser window, you need to compile it using PyInstaller.
### 1. Prerequisites
You **must** use a stable Python release like **Python 3.11** or **3.12**. Pre-release versions (like 3.14) will fail to compile the PyWebView C# dependencies.
Install the required build tools globally for your stable Python version:
```powershell
py -3.11 -m pip install -r requirements.txt pyinstaller
```
### 2. Prepare `app.py`
Before compiling, scroll to the absolute bottom of `app.py` and ensure the standalone runner is active. It should look like this:
```python
if __name__ == '__main__':
init_db()
# For standalone desktop app with embedded browser, use
run_standalone()
# For docker or traditional server deployment, comment out run_standalone() and uncomment the line below:
# socketio.run(app, host='0.0.0.0', port=5000, debug=True)
```
### 3. Compile
Run this exact command in your terminal. It includes the hidden SocketIO threads and bundles your web templates:
```powershell
py -3.11 -m PyInstaller --noconfirm --onedir --windowed --add-data "templates;templates/" --add-data "static;static/" --hidden-import "engineio.async_drivers.threading" --icon "static/favicon.png" app.py
```
### 4. Post-Build
Your portable app will be generated inside the `dist\app` folder.
* To keep your existing inventory, copy your `db/pos_database.db` and `static/cache/` folders from your source code into the new `dist\app` folder before running the `.exe`.
---
## 🐳 Docker Deployment (Server)
Build and run the central inventory server:

64
app.py
View File

@@ -1,4 +1,5 @@
import os
import sys
import sqlite3
import requests
from flask import send_file
@@ -12,6 +13,8 @@ import uuid
from datetime import datetime
import zipfile
import io
import webview
import threading
# from dotenv import load_dotenv
@@ -20,16 +23,42 @@ import io
# MP_ACCESS_TOKEN = os.getenv('MP_ACCESS_TOKEN')
# MP_TERMINAL_ID = os.getenv('MP_TERMINAL_ID')
app = Flask(__name__)
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
socketio = SocketIO(app, cors_allowed_origins="*")
# --- PATH HELPERS FOR PYINSTALLER ---
def get_bundled_path(relative_path):
"""Path for read-only files packed inside the .exe (templates, static)"""
if getattr(sys, 'frozen', False):
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(os.path.dirname(__file__))
return os.path.join(base_path, relative_path)
# Auth Setup
def get_persistent_path(relative_path):
"""Path for read/write files that must survive reboots (db, cache)"""
if getattr(sys, 'frozen', False):
base_path = os.path.dirname(sys.executable)
else:
base_path = os.path.abspath(os.path.dirname(__file__))
return os.path.join(base_path, relative_path)
# --- FLASK INIT ---
app = Flask(
__name__,
template_folder=get_bundled_path('templates'),
static_folder=get_bundled_path('static')
)
app.config['SECRET_KEY'] = 'seki_super_secret_key_99' # Change this if you have actual friends
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
# --- AUTH SETUP (Do not delete this) ---
login_manager = LoginManager(app)
login_manager.login_view = 'login'
DB_FILE = 'db/pos_database.db'
CACHE_DIR = 'static/cache'
# --- DIRECTORY SETUP ---
DB_DIR = get_persistent_path('db')
os.makedirs(DB_DIR, exist_ok=True)
DB_FILE = os.path.join(DB_DIR, "pos_database.db")
CACHE_DIR = get_persistent_path(os.path.join('static', 'cache'))
os.makedirs(CACHE_DIR, exist_ok=True)
# --- MODELS ---
@@ -753,6 +782,29 @@ def delete_gasto(gasto_id):
# })
# return jsonify({"status": "ok"}), 200
def start_server():
# Use socketio.run instead of default app.run
socketio.run(app, host='127.0.0.1', port=5000)
def run_standalone():
t = threading.Thread(target=start_server)
t.daemon = True
t.start()
# GIVE FLASK 2 SECONDS TO BOOT UP BEFORE OPENING THE BROWSER
time.sleep(2)
webview.create_window('SekiPOS', 'http://127.0.0.1:5000', width=1366, height=768, resizable=True, fullscreen=False, min_size=(800, 600), maximized=True)
# private_mode=False is the magic flag that allows localStorage to survive.
# It saves data to %APPDATA%\pywebview on Windows.
webview.start(private_mode=False)
if __name__ == '__main__':
init_db()
# For standalone desktop app with embedded browser, use
#run_standalone()
# For docker or traditional server deployment, comment out run_standalone() and uncomment the line below:
socketio.run(app, host='0.0.0.0', port=5000, debug=True)

45
app.spec Normal file
View File

@@ -0,0 +1,45 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['app.py'],
pathex=[],
binaries=[],
datas=[('templates', 'templates/'), ('static', 'static/')],
hiddenimports=['engineio.async_drivers.threading'],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['static\\favicon.png'],
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='app',
)

2
build/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore

2
dist/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*
!.gitignore