Improve on the template, most things work???
This commit is contained in:
parent
c0f9876184
commit
46fa0d1ae5
19 changed files with 396 additions and 181 deletions
28
README.md
28
README.md
|
|
@ -19,9 +19,33 @@ Open Service Tracker Repair Tool, created as an internal tool for Miracle Comput
|
||||||
- [ ] Simple Authentication
|
- [ ] Simple Authentication
|
||||||
- [ ] OAuth2/LDAP/External Authentication Integration
|
- [ ] OAuth2/LDAP/External Authentication Integration
|
||||||
- [ ] Form and paperwork generation/editor (contains templates for forms/other paperwork to be filled out)
|
- [ ] Form and paperwork generation/editor (contains templates for forms/other paperwork to be filled out)
|
||||||
- [ ] SQLite or full SQL Database support
|
- [ ] SQLite or full SQL Database support (switchable)
|
||||||
- [ ] Mailing for customer repairs and technician appointments
|
- [ ] Mailing for customer repairs and technician appointments
|
||||||
- [ ] Uses SMTP and other information to send mail
|
- [ ] Uses SMTP and other information to send mail
|
||||||
- [ ] Self-hostable & Licensable
|
- [ ] Self-hostable & Licensable
|
||||||
- [ ] Self-hostable for personal/limited commercial use
|
- [ ] Self-hostable for personal/limited commercial use
|
||||||
- [ ] Licensable for full commercial use
|
- [ ] Licensable for full commercial use
|
||||||
|
- [ ] Dockerized for self-hosting
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
1. Clone the repository:
|
||||||
|
```
|
||||||
|
git clone <repository-url>
|
||||||
|
```
|
||||||
|
2. Navigate to the project directory:
|
||||||
|
```
|
||||||
|
cd OpenSTR
|
||||||
|
```
|
||||||
|
3. Install the required dependencies:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
4. Configure the application settings in `config.py`.
|
||||||
|
5. Run the application:
|
||||||
|
```
|
||||||
|
python run.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
- Access the application through your web browser at `http://localhost:5000`.
|
||||||
|
- Use the customer, technician, and admin panels to manage repairs and appointments.
|
||||||
|
|
@ -1,35 +1,20 @@
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_login import LoginManager
|
|
||||||
from flask_migrate import Migrate
|
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
login_manager = LoginManager()
|
|
||||||
migrate = Migrate()
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.config.from_object(Config)
|
app.config.from_object(Config)
|
||||||
|
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
login_manager.init_app(app)
|
|
||||||
migrate.init_app(app, db)
|
from app.routes import customer, technician, admin
|
||||||
|
app.register_blueprint(customer.customer_bp)
|
||||||
# Register blueprints for modular routes
|
app.register_blueprint(technician.technician_bp)
|
||||||
from .routes.admin import admin_bp
|
app.register_blueprint(admin.admin_bp)
|
||||||
from .routes.auth import auth_bp
|
|
||||||
from .routes.customer import customer_bp
|
with app.app_context():
|
||||||
from .routes.repair import repair_bp
|
from app import models
|
||||||
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
|
return app
|
||||||
24
app/forms.py
24
app/forms.py
|
|
@ -0,0 +1,24 @@
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, TextAreaField, DateTimeField, SubmitField
|
||||||
|
from wtforms.validators import DataRequired, Email
|
||||||
|
|
||||||
|
class AppointmentForm(FlaskForm):
|
||||||
|
customer_email = StringField('Customer Email', validators=[DataRequired(), Email()])
|
||||||
|
appointment_date = DateTimeField('Appointment Date', format='%Y-%m-%d %H:%M:%S', validators=[DataRequired()])
|
||||||
|
notes = TextAreaField('Notes', validators=[DataRequired()])
|
||||||
|
submit = SubmitField('Schedule Appointment')
|
||||||
|
|
||||||
|
class RepairNoteForm(FlaskForm):
|
||||||
|
device_id = StringField('Device ID', validators=[DataRequired()])
|
||||||
|
repair_notes = TextAreaField('Repair Notes', validators=[DataRequired()])
|
||||||
|
submit = SubmitField('Submit Repair Notes')
|
||||||
|
|
||||||
|
class UserForm(FlaskForm):
|
||||||
|
username = StringField('Username', validators=[DataRequired()])
|
||||||
|
email = StringField('Email', validators=[DataRequired(), Email()])
|
||||||
|
submit = SubmitField('Create User')
|
||||||
|
|
||||||
|
class RepairBayForm(FlaskForm):
|
||||||
|
name = StringField('Repair Bay Name', validators=[DataRequired()])
|
||||||
|
location = StringField('Location', validators=[DataRequired()])
|
||||||
|
submit = SubmitField('Create Repair Bay')
|
||||||
|
|
@ -1,67 +1,48 @@
|
||||||
from . import db
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_login import UserMixin
|
from app import db
|
||||||
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):
|
class Customer(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(120))
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
email = db.Column(db.String(120), unique=True)
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
phone = db.Column(db.String(20))
|
appointments = db.relationship('Appointment', backref='customer', lazy=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
repairs = db.relationship('Repair', backref='customer', lazy=True)
|
||||||
repairs = db.relationship('RepairJob', backref='customer')
|
|
||||||
|
|
||||||
class Technician(db.Model):
|
class Technician(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(120))
|
username = db.Column(db.String(80), unique=True, nullable=False)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
repairs = db.relationship('Repair', backref='technician', lazy=True)
|
||||||
repairs = db.relationship('RepairJob', backref='technician')
|
|
||||||
|
|
||||||
class RepairJob(db.Model):
|
class Repair(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
tracking_number = db.Column(db.String(50), unique=True)
|
device_info = db.Column(db.String(200), nullable=False)
|
||||||
device = db.Column(db.String(120))
|
status = db.Column(db.String(50), nullable=False)
|
||||||
description = db.Column(db.Text)
|
technician_id = db.Column(db.Integer, db.ForeignKey('technician.id'), nullable=True)
|
||||||
status = db.Column(db.String(50), default='New')
|
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
|
||||||
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'))
|
notes = db.relationship('RepairNote', backref='repair', lazy=True)
|
||||||
technician_id = db.Column(db.Integer, db.ForeignKey('technician.id'))
|
|
||||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
class Appointment(db.Model):
|
||||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
cost_estimate = db.Column(db.Float)
|
date_time = db.Column(db.DateTime, nullable=False)
|
||||||
final_cost = db.Column(db.Float)
|
customer_id = db.Column(db.Integer, db.ForeignKey('customer.id'), nullable=False)
|
||||||
parts_used = db.relationship('RepairPart', backref='repair')
|
|
||||||
barcode_path = db.Column(db.String(256))
|
|
||||||
notes = db.relationship('RepairNote', backref='repair')
|
|
||||||
|
|
||||||
class RepairNote(db.Model):
|
class RepairNote(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
repair_id = db.Column(db.Integer, db.ForeignKey('repair_job.id'))
|
content = db.Column(db.Text, nullable=False)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
repair_id = db.Column(db.Integer, db.ForeignKey('repair.id'), nullable=False)
|
||||||
note = db.Column(db.Text)
|
|
||||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
|
||||||
|
|
||||||
class RepairPart(db.Model):
|
class User(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
repair_id = db.Column(db.Integer, db.ForeignKey('repair_job.id'))
|
username = db.Column(db.String(64), unique=True, nullable=False)
|
||||||
part_id = db.Column(db.Integer, db.ForeignKey('inventory_item.id'))
|
email = db.Column(db.String(120), unique=True, nullable=False)
|
||||||
quantity = db.Column(db.Integer)
|
|
||||||
|
|
||||||
class InventoryItem(db.Model):
|
def __repr__(self):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
return f'<User {self.username}>'
|
||||||
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):
|
class RepairBay(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
action = db.Column(db.String(256))
|
location = db.Column(db.String(100), nullable=False)
|
||||||
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
|
|
||||||
details = db.Column(db.Text)
|
def __repr__(self):
|
||||||
|
return f'<RepairBay {self.name}>'
|
||||||
9
app/routes/__init__.py
Normal file
9
app/routes/__init__.py
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
from flask import Blueprint
|
||||||
|
|
||||||
|
customer_bp = Blueprint('customer', __name__)
|
||||||
|
technician_bp = Blueprint('technician', __name__)
|
||||||
|
admin_bp = Blueprint('admin', __name__)
|
||||||
|
|
||||||
|
from .customer import *
|
||||||
|
from .technician import *
|
||||||
|
from .admin import *
|
||||||
54
app/routes/admin.py
Normal file
54
app/routes/admin.py
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||||
|
from app.forms import UserForm, RepairBayForm
|
||||||
|
from app.models import User, RepairBay
|
||||||
|
from app import db
|
||||||
|
|
||||||
|
admin_bp = Blueprint('admin', __name__)
|
||||||
|
|
||||||
|
@admin_bp.route('/admin/dashboard', methods=['GET', 'POST'])
|
||||||
|
def dashboard():
|
||||||
|
if request.method == 'POST':
|
||||||
|
# Handle form submission for creating/removing users or repair bays
|
||||||
|
pass # Implementation will go here
|
||||||
|
|
||||||
|
users = User.query.all()
|
||||||
|
repair_bays = RepairBay.query.all()
|
||||||
|
return render_template('admin/dashboard.html', users=users, repair_bays=repair_bays)
|
||||||
|
|
||||||
|
@admin_bp.route('/admin/user/create', methods=['GET', 'POST'])
|
||||||
|
def create_user():
|
||||||
|
form = UserForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
new_user = User(username=form.username.data, email=form.email.data)
|
||||||
|
db.session.add(new_user)
|
||||||
|
db.session.commit()
|
||||||
|
flash('User created successfully!', 'success')
|
||||||
|
return redirect(url_for('admin.dashboard'))
|
||||||
|
return render_template('admin/create_user.html', form=form)
|
||||||
|
|
||||||
|
@admin_bp.route('/admin/user/delete/<int:user_id>', methods=['POST'])
|
||||||
|
def delete_user(user_id):
|
||||||
|
user = User.query.get_or_404(user_id)
|
||||||
|
db.session.delete(user)
|
||||||
|
db.session.commit()
|
||||||
|
flash('User deleted successfully!', 'success')
|
||||||
|
return redirect(url_for('admin.dashboard'))
|
||||||
|
|
||||||
|
@admin_bp.route('/admin/repair_bay/create', methods=['GET', 'POST'])
|
||||||
|
def create_repair_bay():
|
||||||
|
form = RepairBayForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
new_bay = RepairBay(name=form.name.data)
|
||||||
|
db.session.add(new_bay)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Repair bay created successfully!', 'success')
|
||||||
|
return redirect(url_for('admin.dashboard'))
|
||||||
|
return render_template('admin/create_repair_bay.html', form=form)
|
||||||
|
|
||||||
|
@admin_bp.route('/admin/repair_bay/delete/<int:bay_id>', methods=['POST'])
|
||||||
|
def delete_repair_bay(bay_id):
|
||||||
|
bay = RepairBay.query.get_or_404(bay_id)
|
||||||
|
db.session.delete(bay)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Repair bay deleted successfully!', 'success')
|
||||||
|
return redirect(url_for('admin.dashboard'))
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
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)
|
|
||||||
|
|
@ -1,11 +1,34 @@
|
||||||
from flask import Blueprint, render_template
|
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||||
from flask_login import login_required, current_user
|
from app.forms import AppointmentForm
|
||||||
from app.models import Customer
|
from app.models import Appointment, Repair, Customer
|
||||||
|
|
||||||
customer_bp = Blueprint('customer', __name__, url_prefix='/customers')
|
customer_bp = Blueprint('customer', __name__)
|
||||||
|
|
||||||
@customer_bp.route('/portal')
|
@customer_bp.route('/dashboard')
|
||||||
@login_required
|
def dashboard():
|
||||||
def portal():
|
# Logic to retrieve customer data, repairs, and appointments
|
||||||
customer = Customer.query.filter_by(user_id=current_user.id).first_or_404()
|
customer_id = 1 # Placeholder for actual customer ID retrieval
|
||||||
return render_template('customers/portal.html', customer=customer)
|
repairs = Repair.query.filter_by(customer_id=customer_id).all()
|
||||||
|
appointments = Appointment.query.filter_by(customer_id=customer_id).all()
|
||||||
|
customer = Customer.query.filter_by(customer_id=customer_id).all()
|
||||||
|
return render_template('customer/dashboard.html', repairs=repairs, appointments=appointments, customer=customer)
|
||||||
|
|
||||||
|
@customer_bp.route('/schedule_appointment', methods=['GET', 'POST'])
|
||||||
|
def schedule_appointment():
|
||||||
|
form = AppointmentForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
new_appointment = Appointment(
|
||||||
|
customer_id=form.customer_id.data,
|
||||||
|
date=form.date.data,
|
||||||
|
time=form.time.data,
|
||||||
|
notes=form.notes.data
|
||||||
|
)
|
||||||
|
# Logic to save the appointment to the database
|
||||||
|
flash('Appointment scheduled successfully!', 'success')
|
||||||
|
return redirect(url_for('customer.dashboard'))
|
||||||
|
return render_template('customer/schedule_appointment.html', form=form)
|
||||||
|
|
||||||
|
@customer_bp.route('/track_progress/<int:repair_id>')
|
||||||
|
def track_progress(repair_id):
|
||||||
|
repair = Repair.query.get_or_404(repair_id)
|
||||||
|
return render_template('customer/track_progress.html', repair=repair)
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
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)
|
|
||||||
24
app/routes/technician.py
Normal file
24
app/routes/technician.py
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
from flask import Blueprint, render_template, request, redirect, url_for, flash
|
||||||
|
from app.models import Repair, Technician
|
||||||
|
from app.forms import RepairNoteForm
|
||||||
|
|
||||||
|
technician_bp = Blueprint('technician', __name__)
|
||||||
|
|
||||||
|
@technician_bp.route('/technician/dashboard')
|
||||||
|
def dashboard():
|
||||||
|
repairs = Repair.query.filter_by(status='in_progress').all()
|
||||||
|
return render_template('technician/dashboard.html', repairs=repairs)
|
||||||
|
|
||||||
|
@technician_bp.route('/technician/repair/<int:repair_id>', methods=['GET', 'POST'])
|
||||||
|
def repair_detail(repair_id):
|
||||||
|
repair = Repair.query.get_or_404(repair_id)
|
||||||
|
form = RepairNoteForm()
|
||||||
|
|
||||||
|
if form.validate_on_submit():
|
||||||
|
repair.notes = form.notes.data
|
||||||
|
repair.status = 'completed'
|
||||||
|
# Add logic to save the repair status and notes
|
||||||
|
flash('Repair notes updated successfully!', 'success')
|
||||||
|
return redirect(url_for('technician.dashboard'))
|
||||||
|
|
||||||
|
return render_template('technician/repair_detail.html', repair=repair, form=form)
|
||||||
29
app/templates/admin/dashboard.html
Normal file
29
app/templates/admin/dashboard.html
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Admin Dashboard</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'base.html' %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Admin Dashboard</h1>
|
||||||
|
<div class="admin-controls">
|
||||||
|
<h2>Manage Repair Bays</h2>
|
||||||
|
<button onclick="location.href='{{ url_for('admin.create_repair_bay') }}'">Create Repair Bay</button>
|
||||||
|
<button onclick="location.href='{{ url_for('admin.remove_repair_bay') }}'">Remove Repair Bay</button>
|
||||||
|
</div>
|
||||||
|
<div class="user-management">
|
||||||
|
<h2>User Management</h2>
|
||||||
|
<button onclick="location.href='{{ url_for('admin.create_user') }}'">Create User</button>
|
||||||
|
<button onclick="location.href='{{ url_for('admin.remove_user') }}'">Remove User</button>
|
||||||
|
</div>
|
||||||
|
<div class="reports">
|
||||||
|
<h2>Reports</h2>
|
||||||
|
<button onclick="location.href='{{ url_for('admin.view_reports') }}'">View Reports</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,26 +1,37 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="UTF-8">
|
||||||
<title>{% block title %}Repair Tracker{% endblock %}</title>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<title>{% block title %}OpenSTR{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom.css') }}">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% include 'navbar.html' %}
|
<header>
|
||||||
<div class="container-fluid mt-3">
|
<h1>Open Service Tracker Repair Tool</h1>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{{ url_for('customer.dashboard') }}">Customer Dashboard</a></li>
|
||||||
|
<li><a href="{{ url_for('technician.dashboard') }}">Technician Dashboard</a></li>
|
||||||
|
<li><a href="{{ url_for('admin.dashboard') }}">Admin Dashboard</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages() %}
|
||||||
{% if messages %}
|
{% if messages %}
|
||||||
<div class="alert alert-info">{{ messages[0] }}</div>
|
<ul class="flashes">
|
||||||
{% endif %}
|
{% for message in messages %}
|
||||||
|
<li>{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</div>
|
</main>
|
||||||
<script src="{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"></script>
|
<footer>
|
||||||
<script src="{{ url_for('static', filename='js/custom.js') }}"></script>
|
<p>© {{ current_year }} Miracle Computer Consulting</p>
|
||||||
|
</footer>
|
||||||
|
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
58
app/templates/customer/dashboard.html
Normal file
58
app/templates/customer/dashboard.html
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Customer Dashboard</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'base.html' %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Welcome to Your Dashboard, {{ customer.name }}</h1>
|
||||||
|
<h2>Your Appointments</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<th>Time</th>
|
||||||
|
<th>Service</th>
|
||||||
|
<th>Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for appointment in appointments %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ appointment.date }}</td>
|
||||||
|
<td>{{ appointment.time }}</td>
|
||||||
|
<td>{{ appointment.service }}</td>
|
||||||
|
<td>{{ appointment.status }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Your Repairs</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Issue</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for repair in repairs %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ repair.device }}</td>
|
||||||
|
<td>{{ repair.issue }}</td>
|
||||||
|
<td>{{ repair.status }}</td>
|
||||||
|
<td>{{ repair.notes }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
44
app/templates/technician/dashboard.html
Normal file
44
app/templates/technician/dashboard.html
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Technician Dashboard</title>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'base.html' %}
|
||||||
|
<div class="container">
|
||||||
|
<h1>Technician Dashboard</h1>
|
||||||
|
<div class="repair-status">
|
||||||
|
<h2>Repair Status Overview</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Submitted Repairs: {{ submitted_count }}</li>
|
||||||
|
<li>In Progress Repairs: {{ in_progress_count }}</li>
|
||||||
|
<li>Completed Repairs: {{ completed_count }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="repair-list">
|
||||||
|
<h2>Repairs</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Device</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Technician Notes</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for repair in repairs %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ repair.device_name }}</td>
|
||||||
|
<td>{{ repair.status }}</td>
|
||||||
|
<td>{{ repair.notes }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
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
|
|
||||||
18
config.py
18
config.py
|
|
@ -2,6 +2,20 @@ import os
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev')
|
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev')
|
||||||
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///repair_tracker.db')
|
# SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL', 'sqlite:///repair_tracker.db')
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db'
|
||||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
UPLOAD_FOLDER = 'app/static/barcodes'
|
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
SECRET_KEY = 'your_secret_key_here'
|
||||||
|
SQLALCHEMY_DATABASE_URI = 'sqlite:///site.db'
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||||
|
|
||||||
|
# Load environment variables if needed
|
||||||
|
# For example, you can use python-dotenv to load variables from a .env file
|
||||||
|
# from dotenv import load_dotenv
|
||||||
|
# load_dotenv()
|
||||||
|
"""
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
Flask
|
Flask==2.2.2
|
||||||
Flask-Login
|
Flask-SQLAlchemy==2.5.1
|
||||||
Flask-WTF
|
Flask-WTF==1.0.0
|
||||||
Flask-SQLAlchemy
|
Flask-Login==0.6.2
|
||||||
Flask-Migrate
|
Flask-Mail==0.9.1
|
||||||
psycopg2-binary
|
python-dotenv==0.20.0
|
||||||
Werkzeug
|
Werkzeug==2.2.2
|
||||||
python-barcode
|
requests==2.28.1
|
||||||
qrcode
|
pytest==7.1.2
|
||||||
Pillow
|
pytest-flask==1.2.0
|
||||||
2
run.py
2
run.py
|
|
@ -3,4 +3,4 @@ from app import create_app
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", port=5000)
|
app.run(host="0.0.0.0", port=5000, debug=True)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue