Create the template for the Flask framework
This commit is contained in:
parent
db65646400
commit
c0f9876184
14 changed files with 266 additions and 0 deletions
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
COPY . /app
|
||||||
|
|
||||||
|
ENV FLASK_APP=run.py
|
||||||
|
ENV FLASK_ENV=production
|
||||||
|
|
||||||
|
CMD ["flask", "run", "--host=0.0.0.0"]
|
||||||
35
app/__init__.py
Normal file
35
app/__init__.py
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_login import LoginManager
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
from config import Config
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
login_manager = LoginManager()
|
||||||
|
migrate = Migrate()
|
||||||
|
|
||||||
|
def create_app():
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Config)
|
||||||
|
|
||||||
|
db.init_app(app)
|
||||||
|
login_manager.init_app(app)
|
||||||
|
migrate.init_app(app, db)
|
||||||
|
|
||||||
|
# Register blueprints for modular routes
|
||||||
|
from .routes.admin import admin_bp
|
||||||
|
from .routes.auth import auth_bp
|
||||||
|
from .routes.customer import customer_bp
|
||||||
|
from .routes.repair import repair_bp
|
||||||
|
from .routes.inventory import inventory_bp
|
||||||
|
from .routes.technician import technician_bp
|
||||||
|
from .routes.audit import audit_bp
|
||||||
|
app.register_blueprint(admin_bp)
|
||||||
|
app.register_blueprint(auth_bp)
|
||||||
|
app.register_blueprint(customer_bp)
|
||||||
|
app.register_blueprint(repair_bp)
|
||||||
|
app.register_blueprint(inventory_bp)
|
||||||
|
app.register_blueprint(technician_bp)
|
||||||
|
app.register_blueprint(audit_bp)
|
||||||
|
|
||||||
|
return app
|
||||||
0
app/forms.py
Normal file
0
app/forms.py
Normal file
67
app/models.py
Normal file
67
app/models.py
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
from . import db
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# User roles: admin, technician, customer
|
||||||
|
class User(db.Model, UserMixin):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
|
password_hash = db.Column(db.String(128), nullable=False)
|
||||||
|
role = db.Column(db.String(20), default='customer')
|
||||||
|
customer = db.relationship('Customer', uselist=False, backref='user')
|
||||||
|
|
||||||
|
class Customer(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(120))
|
||||||
|
email = db.Column(db.String(120), unique=True)
|
||||||
|
phone = db.Column(db.String(20))
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
repairs = db.relationship('RepairJob', backref='customer')
|
||||||
|
|
||||||
|
class Technician(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(120))
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
repairs = db.relationship('RepairJob', backref='technician')
|
||||||
|
|
||||||
|
class RepairJob(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
tracking_number = db.Column(db.String(50), unique=True)
|
||||||
|
device = db.Column(db.String(120))
|
||||||
|
description = db.Column(db.Text)
|
||||||
|
status = db.Column(db.String(50), default='New')
|
||||||
|
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'))
|
||||||
|
technician_id = db.Column(db.Integer, db.ForeignKey('technician.id'))
|
||||||
|
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||||
|
cost_estimate = db.Column(db.Float)
|
||||||
|
final_cost = db.Column(db.Float)
|
||||||
|
parts_used = db.relationship('RepairPart', backref='repair')
|
||||||
|
barcode_path = db.Column(db.String(256))
|
||||||
|
notes = db.relationship('RepairNote', backref='repair')
|
||||||
|
|
||||||
|
class RepairNote(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
repair_id = db.Column(db.Integer, db.ForeignKey('repair_job.id'))
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
note = db.Column(db.Text)
|
||||||
|
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
|
||||||
|
class RepairPart(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
repair_id = db.Column(db.Integer, db.ForeignKey('repair_job.id'))
|
||||||
|
part_id = db.Column(db.Integer, db.ForeignKey('inventory_item.id'))
|
||||||
|
quantity = db.Column(db.Integer)
|
||||||
|
|
||||||
|
class InventoryItem(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
part_name = db.Column(db.String(120))
|
||||||
|
quantity = db.Column(db.Integer)
|
||||||
|
low_stock_threshold = db.Column(db.Integer, default=1)
|
||||||
|
|
||||||
|
class AuditLog(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
action = db.Column(db.String(256))
|
||||||
|
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
||||||
|
details = db.Column(db.Text)
|
||||||
11
app/routes/audit.py
Normal file
11
app/routes/audit.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from flask import Blueprint, render_template
|
||||||
|
from flask_login import login_required
|
||||||
|
from app.models import AuditLog
|
||||||
|
|
||||||
|
audit_bp = Blueprint('audit', __name__, url_prefix='/audit')
|
||||||
|
|
||||||
|
@audit_bp.route('/log')
|
||||||
|
@login_required
|
||||||
|
def log():
|
||||||
|
logs = AuditLog.query.order_by(AuditLog.timestamp.desc()).all()
|
||||||
|
return render_template('audit/log.html', logs=logs)
|
||||||
11
app/routes/customer.py
Normal file
11
app/routes/customer.py
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
from flask import Blueprint, render_template
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from app.models import Customer
|
||||||
|
|
||||||
|
customer_bp = Blueprint('customer', __name__, url_prefix='/customers')
|
||||||
|
|
||||||
|
@customer_bp.route('/portal')
|
||||||
|
@login_required
|
||||||
|
def portal():
|
||||||
|
customer = Customer.query.filter_by(user_id=current_user.id).first_or_404()
|
||||||
|
return render_template('customers/portal.html', customer=customer)
|
||||||
38
app/routes/repair.py
Normal file
38
app/routes/repair.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
from flask import Blueprint, render_template, request, redirect, url_for, flash, current_app
|
||||||
|
from flask_login import login_required, current_user
|
||||||
|
from app.models import db, RepairJob, Customer, Technician, AuditLog
|
||||||
|
from app.utils.barcode import generate_barcode
|
||||||
|
|
||||||
|
repair_bp = Blueprint('repair', __name__, url_prefix='/repairs')
|
||||||
|
|
||||||
|
@repair_bp.route('/new', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def new_repair():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# ... get form data ...
|
||||||
|
repair = RepairJob(
|
||||||
|
device=request.form['device'],
|
||||||
|
description=request.form['description'],
|
||||||
|
customer_id=request.form['customer_id'],
|
||||||
|
technician_id=request.form.get('technician_id'),
|
||||||
|
)
|
||||||
|
db.session.add(repair)
|
||||||
|
db.session.commit()
|
||||||
|
# Generate and save barcode image
|
||||||
|
barcode_path = generate_barcode(repair.id, repair.tracking_number)
|
||||||
|
repair.barcode_path = barcode_path
|
||||||
|
db.session.commit()
|
||||||
|
# Audit log
|
||||||
|
audit = AuditLog(user_id=current_user.id, action='Created repair', details=f"Repair ID {repair.id}")
|
||||||
|
db.session.add(audit)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Repair job created!')
|
||||||
|
return redirect(url_for('repair.detail', repair_id=repair.id))
|
||||||
|
# ... render form ...
|
||||||
|
return render_template('repairs/new.html')
|
||||||
|
|
||||||
|
@repair_bp.route('/<int:repair_id>')
|
||||||
|
@login_required
|
||||||
|
def detail(repair_id):
|
||||||
|
repair = RepairJob.query.get_or_404(repair_id)
|
||||||
|
return render_template('repairs/detail.html', repair=repair)
|
||||||
26
app/templates/base.html
Normal file
26
app/templates/base.html
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{% block title %}Repair Tracker{% endblock %}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
<div class="container-fluid mt-3">
|
||||||
|
{% with messages = get_flashed_messages() %}
|
||||||
|
{% if messages %}
|
||||||
|
<div class="alert alert-info">{{ messages[0] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/custom.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
0
app/utils.py
Normal file
0
app/utils.py
Normal file
16
app/utils/barcode.py
Normal file
16
app/utils/barcode.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
import os
|
||||||
|
import qrcode
|
||||||
|
from barcode import Code128
|
||||||
|
from barcode.writer import ImageWriter
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
def generate_barcode(repair_id, tracking_number):
|
||||||
|
# Generate barcode image
|
||||||
|
barcode = Code128(tracking_number, writer=ImageWriter())
|
||||||
|
barcode_path = os.path.join(current_app.config['UPLOAD_FOLDER'], f'barcode_{repair_id}.png')
|
||||||
|
barcode.save(barcode_path)
|
||||||
|
# Optionally, also generate QR code
|
||||||
|
qr = qrcode.make(tracking_number)
|
||||||
|
qr_path = os.path.join(current_app.config['UPLOAD_FOLDER'], f'qr_{repair_id}.png')
|
||||||
|
qr.save(qr_path)
|
||||||
|
return barcode_path
|
||||||
7
config.py
Normal file
7
config.py
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev')
|
||||||
|
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///repair_tracker.db')
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
UPLOAD_FOLDER = 'app/static/barcodes'
|
||||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgresql://postgres:password@db:5432/repairdb
|
||||||
|
- FLASK_ENV=production
|
||||||
|
- SECRET_KEY=supersecret
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: password
|
||||||
|
POSTGRES_DB: repairdb
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
10
requirements.txt
Normal file
10
requirements.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
Flask
|
||||||
|
Flask-Login
|
||||||
|
Flask-WTF
|
||||||
|
Flask-SQLAlchemy
|
||||||
|
Flask-Migrate
|
||||||
|
psycopg2-binary
|
||||||
|
Werkzeug
|
||||||
|
python-barcode
|
||||||
|
qrcode
|
||||||
|
Pillow
|
||||||
6
run.py
Normal file
6
run.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from app import create_app
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=5000)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue