"""
Simple single-file Business App (Flask + SQLite)
Features:
Products (CRUD)
Customers (CRUD)
Invoices (create, list, view) with invoice items
Basic session-based auth (single admin user)
JSON API endpoints for integration
Run:
pip install flask sqlalchemy
python business_app_flask_singlefile.py
open http://localhost:5000
This file is intentionally a single-file app to make it easy to run and modify.
"""
from flask import Flask, render_template_string, request, redirect, url_for, flash, session, jsonify, abort
from sqlalchemy import create_engine, Column, Integer, String, Float, ForeignKey, DateTime, Text
from sqlalchemy.orm import sessionmaker, relationship, declarative_base
from datetime import datetime
import os
---------------------- Configuration ----------------------
APP_SECRET = os.environ.get('BUSAPP_SECRET', 'change-me-to-a-strong-secret')
ADMIN_USERNAME = os.environ.get('BUSAPP_ADMIN_USER', 'admin')
ADMIN_PASSWORD = os.environ.get('BUSAPP_ADMIN_PASS', 'password')
DB_URL = os.environ.get('BUSAPP_DB', 'sqlite:///business_app.db')
---------------------- App & DB setup ----------------------
app = Flask(name )
app.secret_key = APP_SECRET
engine = create_engine(DB_URL, echo=False, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()
---------------------- Models ----------------------
class Product(Base):
tablename = 'products'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
description = Column(Text, default='')
price = Column(Float, nullable=False, default=0.0)
class Customer(Base):
tablename = 'customers'
id = Column(Integer, primary_key=True)
name = Column(String(200), nullable=False)
email = Column(String(200), default='')
phone = Column(String(100), default='')
address = Column(Text, default='')
class Invoice(Base):
tablename = 'invoices'
id = Column(Integer, primary_key=True)
customer_id = Column(Integer, ForeignKey('customers.id'))
created_at = Column(DateTime, default=datetime.utcnow)
notes = Column(Text, default='')
customer = relationship('Customer')
items = relationship('InvoiceItem', back_populates='invoice', cascade='all, delete-orphan')
class InvoiceItem(Base):
tablename = 'invoice_items'
id = Column(Integer, primary_key=True)
invoice_id = Column(Integer, ForeignKey('invoices.id'))
product_id = Column(Integer, ForeignKey('products.id'), nullable=True)
description = Column(Text, default='')
quantity = Column(Integer, default=1)
unit_price = Column(Float, default=0.0)
invoice = relationship('Invoice', back_populates='items')
product = relationship('Product')
Base.metadata.create_all(engine)
---------------------- Helpers ----------------------
def db_session():
return SessionLocal()
def login_required(fn):
from functools import wraps
@wraps(fn)
def wrapper(*args, **kwargs):
if not session.get('user'):
flash('Please log in to continue', 'warning')
return redirect(url_for('login', next=request.path))
return fn(*args, **kwargs)
return wrapper
---------------------- Templates ----------------------
Minimal base template using Bootstrap CDN for quick UI
base_tpl = """
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for cat, msg in messages %}
{{ msg }}
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
"""
---------------------- Routes ----------------------
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = request.form.get('username')
pw = request.form.get('password')
if user == ADMIN_USERNAME and pw == ADMIN_PASSWORD:
session['user'] = user
flash('Logged in', 'success')
nxt = request.args.get('next') or url_for('index')
return redirect(nxt)
flash('Invalid credentials', 'danger')
return render_template_string(base_tpl + """
{% block content %}
Login
{% endblock %}
""")
@app.route('/logout')
def logout():
session.pop('user', None)
flash('Logged out', 'info')
return redirect(url_for('index'))
@app.route('/')
@login_required
def index():
db = db_session()
product_count = db.query(Product).count()
customer_count = db.query(Customer).count()
invoice_count = db.query(Invoice).count()
last_invoices = db.query(Invoice).order_by(Invoice.created_at.desc()).limit(5).all()
return render_template_string(base_tpl + """
{% block content %}
Products {{ product_count }}
Customers {{ customer_count }}
Invoices {{ invoice_count }}
Recent invoices
{% for inv in last_invoices %}
Invoice #{{ inv.id }} — {{ inv.customer.name if inv.customer else 'Unknown' }} — {{ inv.created_at.strftime('%Y-%m-%d') }}
{% endfor %}
{% endblock %}
""", product_count=product_count, customer_count=customer_count, invoice_count=invoice_count, last_invoices=last_invoices)
----- Products CRUD -----
@app.route('/products')
@login_required
def products():
db = db_session()
items = db.query(Product).order_by(Product.name).all()
return render_template_string(base_tpl + """
{% block content %}
{% for p in items %}{% endfor %}
Name Price {{ p.name }} {{ '%.2f'|format(p.price) }} Edit Delete
{% endblock %}
""", items=items)
@app.route('/products/add', methods=['GET','POST'])
@login_required
def add_product():
if request.method == 'POST':
name = request.form.get('name')
price = float(request.form.get('price') or 0)
desc = request.form.get('description')
db = db_session()
p = Product(name=name, price=price, description=desc)
db.add(p); db.commit()
flash('Product added', 'success')
return redirect(url_for('products'))
return render_template_string(base_tpl + """
{% block content %}
Add product
{% endblock %}
""")
@app.route('/products/
int:product_id /edit', methods=['GET','POST'])
@login_required
def edit_product(product_id):
db = db_session()
p = db.query(Product).get(product_id)
if not p: abort(404)
if request.method == 'POST':
p.name = request.form.get('name')
p.price = float(request.form.get('price') or 0)
p.description = request.form.get('description')
db.commit()
flash('Product updated', 'success')
return redirect(url_for('products'))
return render_template_string(base_tpl + """
{% block content %}
Edit product
Name
Price
Description {{ p.description }}
Save
{% endblock %}
""", p=p)
@app.route('/products/
int:product_id /delete')
@login_required
def delete_product(product_id):
db = db_session()
p = db.query(Product).get(product_id)
if p:
db.delete(p); db.commit(); flash('Deleted', 'info')
return redirect(url_for('products'))
----- Customers CRUD -----
@app.route('/customers')
@login_required
def customers():
db = db_session()
items = db.query(Customer).order_by(Customer.name).all()
return render_template_string(base_tpl + """
{% block content %}
{% for c in items %}{% endfor %}
{% endblock %}
""", items=items)
@app.route('/customers/add', methods=['GET','POST'])
@login_required
def add_customer():
if request.method == 'POST':
name = request.form.get('name')
email = request.form.get('email')
phone = request.form.get('phone')
address = request.form.get('address')
db = db_session()
c = Customer(name=name, email=email, phone=phone, address=address)
db.add(c); db.commit()
flash('Customer added', 'success')
return redirect(url_for('customers'))
return render_template_string(base_tpl + """
{% block content %}
Add customer
Name
Email
Phone
Address
Add
{% endblock %}
""")
@app.route('/customers/
int:customer_id /edit', methods=['GET','POST'])
@login_required
def edit_customer(customer_id):
db = db_session()
c = db.query(Customer).get(customer_id)
if not c: abort(404)
if request.method == 'POST':
c.name = request.form.get('name')
c.email = request.form.get('email')
c.phone = request.form.get('phone')
c.address = request.form.get('address')
db.commit(); flash('Customer updated', 'success')
return redirect(url_for('customers'))
return render_template_string(base_tpl + """
{% block content %}
Edit customer
Name
Email
Phone
Address {{ c.address }}
Save
{% endblock %}
""", c=c)
@app.route('/customers/
int:customer_id /delete')
@login_required
def delete_customer(customer_id):
db = db_session()
c = db.query(Customer).get(customer_id)
if c:
db.delete(c); db.commit(); flash('Deleted', 'info')
return redirect(url_for('customers'))
----- Invoices -----
@app.route('/invoices')
@login_required
def invoices():
db = db_session()
items = db.query(Invoice).order_by(Invoice.created_at.desc()).all()
return render_template_string(base_tpl + """
{% block content %}
{% for i in items %}{% endfor %}
# Customer Date Total {{ i.id }} {{ i.customer.name if i.customer else 'Unknown' }} {{ i.created_at.strftime('%Y-%m-%d') }} {{ '%.2f'|format(sum([it.quantity*it.unit_price for it in i.items])) }} View
{% endblock %}
""", items=items)
@app.route('/invoices/create', methods=['GET','POST'])
@login_required
def create_invoice():
db = db_session()
products = db.query(Product).order_by(Product.name).all()
customers = db.query(Customer).order_by(Customer.name).all()
if request.method == 'POST':
customer_id = int(request.form.get('customer_id') or 0)
notes = request.form.get('notes')
inv = Invoice(customer_id=customer_id if customer_id>0 else None, notes=notes)
db.add(inv); db.commit()
# Items: form fields like item-desc-1, item-qty-1, item-price-1
idx = 1
while True:
desc = request.form.get(f'item-desc-{idx}')
if not desc:
break
qty = int(request.form.get(f'item-qty-{idx}') or 1)
up = float(request.form.get(f'item-price-{idx}') or 0)
ii = InvoiceItem(invoice_id=inv.id, description=desc, quantity=qty, unit_price=up)
db.add(ii)
idx += 1
db.commit()
flash('Invoice created', 'success')
return redirect(url_for('invoices'))
return render_template_string(base_tpl + """
{% block content %}
Create invoice
Customer -- none -- {% for c in customers %}{{ c.name }} {% endfor %}
Notes
Items
Add item
Create
container.insertAdjacentHTML('beforeend', html)
idx++
}
{% endblock %}
""", products=products, customers=customers)
@app.route('/invoices/
int:invoice_id ')
@login_required
def view_invoice(invoice_id):
db = db_session()
inv = db.query(Invoice).get(invoice_id)
if not inv: abort(404)
total = sum([it.quantity*it.unit_price for it in inv.items])
return render_template_string(base_tpl + """
{% block content %}
Invoice #{{ inv.id }}
Customer: {{ inv.customer.name if inv.customer else '—' }}
Date: {{ inv.created_at.strftime('%Y-%m-%d') }}
{% for it in inv.items %}{% endfor %}
Description Qty Unit Line {{ it.description }} {{ it.quantity }} {{ '%.2f'|format(it.unit_price) }} {{ '%.2f'|format(it.quantity * it.unit_price) }} Total {{ '%.2f'|format(total) }}
{% endblock %}
""", inv=inv, total=total)
----- Simple JSON API endpoints