Added service plan. Password change

This commit is contained in:
2025-09-06 12:28:37 -04:00
parent cd3f4471cc
commit a280194079
5 changed files with 230 additions and 12 deletions

View File

@@ -86,6 +86,10 @@ def login():
if not bcrypt.check_password_hash(user.password_hash, password): if not bcrypt.check_password_hash(user.password_hash, password):
return jsonify({"error": "Invalid password"}), 401 # Use a more descriptive error and status code return jsonify({"error": "Invalid password"}), 401 # Use a more descriptive error and status code
# Check if user is active
if user.active != 1:
return jsonify({"error": "Please contact a manager. Login rejected"}), 401
# If login is successful, return the correct structure # If login is successful, return the correct structure
return jsonify({ return jsonify({
"ok": True, "ok": True,
@@ -168,17 +172,21 @@ def register_user():
@auth.route('/change-password', methods=['POST']) @auth.route('/change-password', methods=['POST'])
@login_required
def change_password(): def change_password():
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({"error": "Authorization header missing"}), 401
api_key = re.sub(r'^bearer\s+', '', auth_header, flags=re.IGNORECASE).strip('"')
user = db.session.query(Auth_User).filter(Auth_User.api_key == api_key).first()
if not user:
return jsonify({"error": "Invalid token"}), 401
new_password = request.json["new_password"] new_password = request.json["new_password"]
new_password_confirm = request.json["password_confirm"] new_password_confirm = request.json["password_confirm"]
user = db.session\
.query(Auth_User) \
.filter(Auth_User.id == current_user.id) \
.first()
if str(new_password) != str(new_password_confirm): if str(new_password) != str(new_password_confirm):
return jsonify({"error": "Error: Incorrect Passwords"}), 200 return jsonify({"error": "Error: Incorrect Passwords"}), 200
@@ -190,5 +198,49 @@ def change_password():
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
return jsonify({"ok": "success"}), 200 return jsonify({"ok": True}), 200
@auth.route('/admin-change-password', methods=['POST'])
def admin_change_password():
auth_header = request.headers.get('Authorization')
if not auth_header:
return jsonify({"error": "Authorization header missing"}), 401
api_key = re.sub(r'^bearer\s+', '', auth_header, flags=re.IGNORECASE).strip('"')
user = db.session.query(Auth_User).filter(Auth_User.api_key == api_key).first()
if not user:
return jsonify({"error": "Invalid token"}), 401
if user.admin_role != 0:
return jsonify({"error": "Admin access required"}), 403
employee_id = request.json.get("employee_id")
new_password = request.json.get("new_password")
new_password_confirm = request.json.get("password_confirm")
if not employee_id or not new_password or not new_password_confirm:
return jsonify({"error": "Missing required fields"}), 400
if str(new_password) != str(new_password_confirm):
return jsonify({"error": "Passwords do not match"}), 400
from app.classes.employee import Employee_Employee
employee = db.session.query(Employee_Employee).filter(Employee_Employee.id == employee_id).first()
if not employee:
return jsonify({"error": "Employee not found"}), 404
target_user = db.session.query(Auth_User).filter(Auth_User.id == employee.user_id).first()
if not target_user:
return jsonify({"error": "User not found"}), 404
hashed_password = bcrypt.generate_password_hash(new_password).decode('utf-8')
target_user.password_hash = hashed_password
target_user.passwordpinallowed = 0
db.session.add(target_user)
db.session.commit()
return jsonify({"ok": True}), 200

View File

@@ -26,6 +26,7 @@ class Auth_User(UserMixin, db.Model):
admin = db.Column(db.INTEGER) admin = db.Column(db.INTEGER)
admin_role = db.Column(db.INTEGER) admin_role = db.Column(db.INTEGER)
confirmed = db.Column(db.INTEGER) confirmed = db.Column(db.INTEGER)
active = db.Column(db.INTEGER, default=1)
def __init__(self, def __init__(self,
username, username,
@@ -37,6 +38,7 @@ class Auth_User(UserMixin, db.Model):
admin, admin,
admin_role, admin_role,
confirmed, confirmed,
active=1,
): ):
self.username = username self.username = username
self.api_key = api_key self.api_key = api_key
@@ -47,6 +49,7 @@ class Auth_User(UserMixin, db.Model):
self.admin = admin self.admin = admin
self.admin_role = admin_role self.admin_role = admin_role
self.confirmed = confirmed self.confirmed = confirmed
self.active = active
def is_authenticated(self): def is_authenticated(self):
return True return True
@@ -75,4 +78,4 @@ class AnonymousUser(AnonymousUserMixin):
self.username = 'Guest' self.username = 'Guest'
login_manager.anonymous_user = AnonymousUser login_manager.anonymous_user = AnonymousUser

View File

@@ -60,4 +60,26 @@ class Service_Parts_schema(ma.SQLAlchemyAutoSchema):
class Meta: class Meta:
model = Service_Parts model = Service_Parts
scheduled_date = ma.DateTime(format='%Y-%m-%dT%H:%M:%S') scheduled_date = ma.DateTime(format='%Y-%m-%dT%H:%M:%S')
when_ordered = ma.DateTime(format='%Y-%m-%dT%H:%M:%S') when_ordered = ma.DateTime(format='%Y-%m-%dT%H:%M:%S')
class Service_Plans(db.Model):
__tablename__ = 'service_plans'
__table_args__ = {"schema": "public"}
id = db.Column(db.Integer,
primary_key=True,
autoincrement=True,
unique=False)
customer_id = db.Column(db.INTEGER)
contract_plan = db.Column(db.INTEGER, default=0) # 0=no contract, 1=standard, 2=premium
contract_years = db.Column(db.INTEGER, default=1)
contract_start_date = db.Column(db.DATE())
class Service_Plans_schema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Service_Plans
contract_start_date = ma.DateTime(format='%Y-%m-%d')

View File

@@ -6,6 +6,7 @@ from flask_login import login_required
from app.employees import employees from app.employees import employees
from app import db from app import db
from app.classes.employee import Employee_Employee, Employee_Employee_schema from app.classes.employee import Employee_Employee, Employee_Employee_schema
from app.classes.auth import Auth_User
from app.classes.stats_employee import Stats_Employee_Oil, Stats_Employee_Office from app.classes.stats_employee import Stats_Employee_Oil, Stats_Employee_Office
@employees.route("/<int:userid>", methods=["GET"]) @employees.route("/<int:userid>", methods=["GET"])
@@ -13,7 +14,26 @@ from app.classes.stats_employee import Stats_Employee_Oil, Stats_Employee_Office
def get_specific_employee(userid): def get_specific_employee(userid):
employee = db.session \ employee = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
.filter(Employee_Employee.id == userid) \ .filter(Employee_Employee.user_id == userid) \
.first()
# Get active status from Auth_User
user = db.session.query(Auth_User).filter(Auth_User.id == userid).first()
active_status = user.active if user else 1
employee_schema = Employee_Employee_schema(many=False)
employee_data = employee_schema.dump(employee)
employee_data['active'] = active_status
return jsonify(employee_data)
@employees.route("/byid/<int:employee_id>", methods=["GET"])
@login_required
def get_employee_by_id(employee_id):
employee = db.session \
.query(Employee_Employee) \
.filter(Employee_Employee.id == employee_id) \
.first() .first()
employee_schema = Employee_Employee_schema(many=False) employee_schema = Employee_Employee_schema(many=False)
return jsonify(employee_schema.dump(employee)) return jsonify(employee_schema.dump(employee))
@@ -169,7 +189,7 @@ def employee_edit(employee_id):
e_type = request.json["employee_type"] e_type = request.json["employee_type"]
e_start_date = request.json["employee_start_date"] e_start_date = request.json["employee_start_date"]
e_end_date = request.json["employee_end_date"] e_end_date = request.json["employee_end_date"]
e_active = request.json.get("active", 1)
get_employee = db.session \ get_employee = db.session \
.query(Employee_Employee) \ .query(Employee_Employee) \
@@ -187,6 +207,12 @@ def employee_edit(employee_id):
if e_end_date != 'None': if e_end_date != 'None':
get_employee.employee_end_date = e_end_date get_employee.employee_end_date = e_end_date
# Update active status in Auth_User
user = db.session.query(Auth_User).filter(Auth_User.id == get_employee.user_id).first()
if user:
user.active = int(e_active)
db.session.add(user)
db.session.add(get_employee) db.session.add(get_employee)
db.session.commit() db.session.commit()

View File

@@ -4,7 +4,8 @@ from app import db
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
from app.classes.customer import (Customer_Customer) from app.classes.customer import (Customer_Customer)
from app.classes.service import (Service_Service, from app.classes.service import (Service_Service,
Service_Service_schema, Service_Parts, Service_Parts_schema Service_Service_schema, Service_Parts, Service_Parts_schema,
Service_Plans, Service_Plans_schema
) )
@@ -173,6 +174,120 @@ def update_service_call(id):
db.session.rollback() db.session.rollback()
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
# Service Plans CRUD endpoints
@service.route("/plans/active", methods=["GET"])
def get_active_service_plans():
"""
Get all active service plans (contract_plan > 0)
"""
try:
plans = Service_Plans.query.filter(Service_Plans.contract_plan > 0).all()
plans_schema = Service_Plans_schema(many=True)
result = plans_schema.dump(plans)
# Add customer info to each plan
for plan in result:
customer = Customer_Customer.query.get(plan['customer_id'])
if customer:
plan['customer_name'] = f"{customer.customer_first_name} {customer.customer_last_name}"
plan['customer_address'] = customer.customer_address
plan['customer_town'] = customer.customer_town
return jsonify(result), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@service.route("/plans/customer/<int:customer_id>", methods=["GET"])
def get_customer_service_plan(customer_id):
"""
Get service plan for a specific customer
"""
try:
plan = Service_Plans.query.filter_by(customer_id=customer_id).first()
if plan:
plan_schema = Service_Plans_schema()
return jsonify(plan_schema.dump(plan)), 200
else:
return jsonify(None), 200
except Exception as e:
return jsonify({"error": str(e)}), 500
@service.route("/plans/create", methods=["POST"])
def create_service_plan():
"""
Create a new service plan for a customer
"""
data = request.get_json()
if not data:
return jsonify({"error": "No data provided"}), 400
try:
new_plan = Service_Plans(
customer_id=data['customer_id'],
contract_plan=data['contract_plan'],
contract_years=data['contract_years'],
contract_start_date=datetime.fromisoformat(data['contract_start_date'])
)
db.session.add(new_plan)
db.session.commit()
plan_schema = Service_Plans_schema()
return jsonify({"ok": True, "plan": plan_schema.dump(new_plan)}), 201
except Exception as e:
db.session.rollback()
return jsonify({"error": str(e)}), 500
@service.route("/plans/update/<int:customer_id>", methods=["PUT"])
def update_service_plan(customer_id):
"""
Update existing service plan for a customer
"""
data = request.get_json()
if not data:
return jsonify({"error": "No data provided"}), 400
try:
plan = Service_Plans.query.filter_by(customer_id=customer_id).first()
if not plan:
# Create new plan if it doesn't exist
plan = Service_Plans(customer_id=customer_id)
db.session.add(plan)
plan.contract_plan = data.get('contract_plan', plan.contract_plan)
plan.contract_years = data.get('contract_years', plan.contract_years)
if data.get('contract_start_date'):
plan.contract_start_date = datetime.fromisoformat(data['contract_start_date'])
db.session.commit()
plan_schema = Service_Plans_schema()
return jsonify({"ok": True, "plan": plan_schema.dump(plan)}), 200
except Exception as e:
db.session.rollback()
return jsonify({"error": str(e)}), 500
@service.route("/plans/delete/<int:customer_id>", methods=["DELETE"])
def delete_service_plan(customer_id):
"""
Delete service plan for a customer
"""
try:
plan = Service_Plans.query.filter_by(customer_id=customer_id).first()
if not plan:
return jsonify({"error": "Service plan not found"}), 404
db.session.delete(plan)
db.session.commit()
return jsonify({"ok": True, "message": "Service plan deleted successfully"}), 200
except Exception as e:
db.session.rollback()
return jsonify({"error": str(e)}), 500
@service.route("/delete/<int:id>", methods=["DELETE"]) @service.route("/delete/<int:id>", methods=["DELETE"])
def delete_service_call(id): def delete_service_call(id):
service_record = Service_Service.query.get_or_404(id) service_record = Service_Service.query.get_or_404(id)