diff --git a/app/auth/views.py b/app/auth/views.py index c944f1f..9ebfb16 100755 --- a/app/auth/views.py +++ b/app/auth/views.py @@ -86,6 +86,10 @@ def login(): if not bcrypt.check_password_hash(user.password_hash, password): 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 return jsonify({ "ok": True, @@ -168,17 +172,21 @@ def register_user(): @auth.route('/change-password', methods=['POST']) -@login_required 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_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): return jsonify({"error": "Error: Incorrect Passwords"}), 200 @@ -190,5 +198,49 @@ def change_password(): db.session.add(user) 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 diff --git a/app/classes/auth.py b/app/classes/auth.py index d362188..c080aed 100755 --- a/app/classes/auth.py +++ b/app/classes/auth.py @@ -26,6 +26,7 @@ class Auth_User(UserMixin, db.Model): admin = db.Column(db.INTEGER) admin_role = db.Column(db.INTEGER) confirmed = db.Column(db.INTEGER) + active = db.Column(db.INTEGER, default=1) def __init__(self, username, @@ -37,6 +38,7 @@ class Auth_User(UserMixin, db.Model): admin, admin_role, confirmed, + active=1, ): self.username = username self.api_key = api_key @@ -47,6 +49,7 @@ class Auth_User(UserMixin, db.Model): self.admin = admin self.admin_role = admin_role self.confirmed = confirmed + self.active = active def is_authenticated(self): return True @@ -75,4 +78,4 @@ class AnonymousUser(AnonymousUserMixin): self.username = 'Guest' -login_manager.anonymous_user = AnonymousUser \ No newline at end of file +login_manager.anonymous_user = AnonymousUser diff --git a/app/classes/service.py b/app/classes/service.py index b5c1291..0a1f3a7 100644 --- a/app/classes/service.py +++ b/app/classes/service.py @@ -60,4 +60,26 @@ class Service_Parts_schema(ma.SQLAlchemyAutoSchema): class Meta: model = Service_Parts scheduled_date = ma.DateTime(format='%Y-%m-%dT%H:%M:%S') - when_ordered = ma.DateTime(format='%Y-%m-%dT%H:%M:%S') \ No newline at end of file + 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') diff --git a/app/employees/views.py b/app/employees/views.py index 8ed3a11..113c30f 100755 --- a/app/employees/views.py +++ b/app/employees/views.py @@ -6,6 +6,7 @@ from flask_login import login_required from app.employees import employees from app import db 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 @employees.route("/", methods=["GET"]) @@ -13,7 +14,26 @@ from app.classes.stats_employee import Stats_Employee_Oil, Stats_Employee_Office def get_specific_employee(userid): employee = db.session \ .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/", methods=["GET"]) +@login_required +def get_employee_by_id(employee_id): + employee = db.session \ + .query(Employee_Employee) \ + .filter(Employee_Employee.id == employee_id) \ .first() employee_schema = Employee_Employee_schema(many=False) return jsonify(employee_schema.dump(employee)) @@ -169,7 +189,7 @@ def employee_edit(employee_id): e_type = request.json["employee_type"] e_start_date = request.json["employee_start_date"] e_end_date = request.json["employee_end_date"] - + e_active = request.json.get("active", 1) get_employee = db.session \ .query(Employee_Employee) \ @@ -187,6 +207,12 @@ def employee_edit(employee_id): if e_end_date != 'None': 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.commit() diff --git a/app/service/views.py b/app/service/views.py index cd33954..a2e4e73 100644 --- a/app/service/views.py +++ b/app/service/views.py @@ -4,7 +4,8 @@ from app import db from datetime import datetime, date, timedelta from app.classes.customer import (Customer_Customer) 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() 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/", 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/", 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/", 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/", methods=["DELETE"]) def delete_service_call(id): service_record = Service_Service.query.get_or_404(id)