from flask import render_template, redirect, request, session, jsonify, flash, url_for from flask_login import login_required, current_user, logout_user, login_user from datetime import datetime from app.models import Category def register_main_routes(app): """Register main web application routes""" @app.before_request def debug_authentication_state(): """Debug any automatic authentication that might be happening""" from flask_login import current_user from flask import request, session # Only log for web routes (not API routes) if not request.path.startswith('/api/'): print(f"🔍 Route: {request.path}") print(f"🔍 current_user.is_authenticated: {current_user.is_authenticated}") print(f"🔍 session keys: {list(session.keys())}") if current_user.is_authenticated: print(f"⚠️ User automatically authenticated: {current_user.email}") @app.route('/') def index(): # Serve the complete marketing page directly with open('app/templates/index.html', 'r') as f: return f.read() @app.route('/test') def test(): """Simple test route""" return "

✅ RateRight Test Route Working!

If you see this, the Flask server is responding correctly.

" @app.route('/applications') @login_required def my_applications(): """View user's job applications or received applications""" from app.models import Application from app.extensions import db if current_user.role == 'worker': applications = Application.query.filter_by(worker_id=current_user.id).order_by(Application.id.desc()).all() return render_template('applications/list.html', applications=applications) elif current_user.role == 'contractor': # Get all applications for contractor's jobs from app.models import Job applications = db.session.query(Application).join(Job).filter( Job.contractor_id == current_user.id ).order_by(Application.id.desc()).all() return render_template('applications/contractor_list.html', applications=applications) else: flash('Access denied.', 'error') return redirect(url_for('dashboard')) @app.route('/jobs') def browse_jobs(): """Browse available construction jobs""" if current_user.is_authenticated and current_user.role == 'contractor': flash('As a contractor, you can post jobs and review applications. Workers browse and apply for jobs.', 'info') return render_template('jobs/browse.html') @app.route('/jobs/post', methods=['GET', 'POST']) def post_job(): """Post a new construction job (requires login)""" from flask_login import current_user from app.models import Job, Category from app.extensions import db from decimal import Decimal if not current_user.is_authenticated: flash('Please login to post a job', 'warning') return redirect(url_for('login')) if current_user.role != 'contractor': flash('Only contractors can post jobs', 'error') return redirect(url_for('dashboard')) if request.method == 'POST': try: # Get form data title = request.form.get('title', '').strip() description = request.form.get('description', '').strip() category_id = request.form.get('category_id', type=int) location = request.form.get('location', '').strip() budget_min = request.form.get('budget_min') budget_max = request.form.get('budget_max') hourly_rate = request.form.get('hourly_rate') whs_requirements = request.form.get('whs_requirements', '').strip() white_card_required = 'white_card_required' in request.form insurance_required = 'insurance_required' in request.form # Validation errors = [] if not title: errors.append('Job title is required') if not description: errors.append('Job description is required') if not category_id: errors.append('Job category is required') if not location: errors.append('Job location is required') # Validate category exists if category_id: category = Category.query.get(category_id) if not category or not category.is_active: errors.append('Invalid job category selected') # Convert budget/rate to Decimal if provided budget_min_decimal = None budget_max_decimal = None hourly_rate_decimal = None try: if budget_min: budget_min_decimal = Decimal(str(budget_min)) if budget_min_decimal < 0: errors.append('Minimum budget cannot be negative') except (ValueError, TypeError): errors.append('Invalid minimum budget amount') try: if budget_max: budget_max_decimal = Decimal(str(budget_max)) if budget_max_decimal < 0: errors.append('Maximum budget cannot be negative') except (ValueError, TypeError): errors.append('Invalid maximum budget amount') try: if hourly_rate: hourly_rate_decimal = Decimal(str(hourly_rate)) if hourly_rate_decimal < 0: errors.append('Hourly rate cannot be negative') except (ValueError, TypeError): errors.append('Invalid hourly rate amount') # Validate budget range if budget_min_decimal and budget_max_decimal and budget_min_decimal > budget_max_decimal: errors.append('Minimum budget cannot be greater than maximum budget') if errors: for error in errors: flash(error, 'error') categories = Category.query.filter_by(is_active=True).order_by(Category.sort_order, Category.name).all() return render_template('jobs/post.html', categories=categories) # Create new job job = Job( title=title, description=description, contractor_id=current_user.id, category_id=category_id, location=location, budget_min=budget_min_decimal, budget_max=budget_max_decimal, hourly_rate=hourly_rate_decimal, whs_requirements=whs_requirements or f"Category: {category.name}. Risk Level: {category.whs_risk_level}.", white_card_required=white_card_required, insurance_required=insurance_required, status='open' ) db.session.add(job) db.session.commit() flash('Job posted successfully!', 'success') return redirect(url_for('job_details', job_id=job.id)) except Exception as e: db.session.rollback() flash(f'Error posting job: {str(e)}', 'error') categories = Category.query.filter_by(is_active=True).order_by(Category.sort_order, Category.name).all() return render_template('jobs/post.html', categories=categories) # GET request - show the form categories = Category.query.filter_by(is_active=True).order_by(Category.sort_order, Category.name).all() return render_template('jobs/post.html', categories=categories) @app.route('/jobs/') def job_details(job_id): """View specific job details""" from app.models import Job, Application job = Job.query.get_or_404(job_id) # Retrieve job from database, or return 404 # Check if current user has already applied to this job user_applied = False if current_user.is_authenticated and current_user.role == 'worker': existing_app = Application.query.filter_by( job_id=job_id, worker_id=current_user.id ).first() user_applied = existing_app is not None return render_template('jobs/details.html', job=job, user_applied=user_applied) @app.route('/jobs//apply', methods=['POST']) @login_required def apply_to_job(job_id): """Apply to a job""" if request.method == 'POST': from app.models import Application from app.extensions import db from app.models import Job # Import Job model if current_user.role != 'worker': flash('Only workers can apply to jobs.', 'error') return redirect(url_for('job_details', job_id=job_id)) job = Job.query.get_or_404(job_id) if job.status != 'open': flash('This job is no longer accepting applications.', 'error') return redirect(url_for('job_details', job_id=job_id)) # Check if already applied existing_app = Application.query.filter_by( job_id=job_id, worker_id=current_user.id ).first() if existing_app: flash('You have already applied to this job.', 'warning') return redirect(url_for('job_details', job_id=job_id)) # Get form data proposed_rate = request.form.get('proposed_rate') cover_letter = request.form.get('cover_letter', '').strip() # Validate proposed rate proposed_rate_decimal = None if proposed_rate: try: proposed_rate_decimal = float(proposed_rate) if proposed_rate_decimal < 0: flash('Proposed rate cannot be negative.', 'error') return redirect(url_for('job_details', job_id=job_id)) except (ValueError, TypeError): flash('Invalid proposed rate.', 'error') return redirect(url_for('job_details', job_id=job_id)) # Create application application = Application( job_id=job_id, worker_id=current_user.id, proposed_rate=proposed_rate_decimal, cover_letter=cover_letter, abn_verified=True, # Auto-verify for now insurance_verified=current_user.public_liability_insurance, status='pending' ) try: db.session.add(application) # Update job applications count job.applications_count = (job.applications_count or 0) + 1 db.session.commit() flash('Application submitted successfully!', 'success') return redirect(url_for('dashboard')) except Exception as e: db.session.rollback() flash(f'Error submitting application: {str(e)}', 'error') print(f"Database error: {e}") # For debugging return redirect(url_for('job_details', job_id=job_id)) @app.route('/login', methods=['GET', 'POST']) def login(): """User login page and web form handler""" from app.models.user import User if current_user.is_authenticated: return redirect(url_for('dashboard')) if request.method == 'POST': # Handle web form login (creates Flask-Login session) email = request.form.get('email') password = request.form.get('password') if not email or not password: flash('Email and password are required', 'error') return render_template('auth/login.html') user = User.query.filter_by(email=email).first() if user and user.check_password(password) and user.is_active: # Create Flask-Login session login_user(user, remember=request.form.get('remember')) flash('Login successful!', 'success') return redirect(url_for('dashboard')) else: flash('Invalid email or password', 'error') return render_template('auth/login.html') @app.route('/register', methods=['GET', 'POST']) def register(): """User registration page and handler""" if current_user.is_authenticated: return redirect(url_for('dashboard')) if request.method == 'POST': from app.models.user import User from app.extensions import db # Get form data email = request.form.get('email', '').strip().lower() password = request.form.get('password') confirm_password = request.form.get('confirm_password') first_name = request.form.get('first_name', '').strip() last_name = request.form.get('last_name', '').strip() role = request.form.get('role') phone_number = request.form.get('phone_number', '').strip() location = request.form.get('location', '').strip() abn_number = request.form.get('abn_number', '').strip() # Validation errors = [] if not email: errors.append('Email is required') elif User.query.filter_by(email=email).first(): errors.append('Email already registered') if not password or len(password) < 6: errors.append('Password must be at least 6 characters') elif password != confirm_password: errors.append('Passwords do not match') if not first_name: errors.append('First name is required') if not last_name: errors.append('Last name is required') if role not in ['contractor', 'worker']: errors.append('Please select a valid role') if not phone_number: errors.append('Phone number is required') if not location: errors.append('Location is required') if not abn_number or len(abn_number) != 11: errors.append('Valid 11-digit ABN is required') if errors: for error in errors: flash(error, 'error') return render_template('auth/register.html') try: # Create new user user = User( email=email, first_name=first_name, last_name=last_name, role=role, phone_number=phone_number, location=location, abn_number=abn_number, privacy_consent=True, terms_accepted=True, terms_accepted_date=datetime.utcnow() ) user.set_password(password) db.session.add(user) db.session.commit() login_user(user) flash('Registration successful! Welcome to RateRight!', 'success') return redirect(url_for('dashboard')) except Exception as e: db.session.rollback() flash(f'Registration failed: {str(e)}', 'error') return render_template('auth/register.html') return render_template('auth/register.html') @app.route('/logout') def logout(): """Logout user and completely clear all sessions""" from flask import make_response logout_user() # Clear Flask-Login session session.clear() # Clear Flask session # Create response that clears everything response = make_response(''' Logging out...

Logging out...

''') # Clear all cookies by setting them to expire response.set_cookie('session', '', expires=0) response.set_cookie('remember_token', '', expires=0) return response @app.route('/dashboard') def dashboard(): """User dashboard - redirect based on role""" # Check if user is logged in via Flask-Login if not current_user.is_authenticated: return redirect(url_for('login')) # Redirect based on user role return redirect(url_for(f'dashboard_{current_user.role}')) @app.route('/dashboard/contractor') def dashboard_contractor(): """Contractor dashboard""" from app.models import Job, Application, Contract from app.extensions import db if not current_user.is_authenticated or current_user.role != 'contractor': return redirect(url_for('login')) # Count of posted jobs (active only) job_count = Job.query.filter_by( contractor_id=current_user.id, status='open' ).count() # Pending applications that need review pending_applications = db.session.query(Application).join(Job).filter( Job.contractor_id == current_user.id, Application.status == 'pending' ).order_by(Application.date_applied.desc()).limit(5).all() # Recently processed applications (history) processed_applications = db.session.query(Application).join(Job).filter( Job.contractor_id == current_user.id, Application.status.in_(['accepted', 'rejected']) ).order_by(Application.date_applied.desc()).limit(5).all() # Calculate metrics total_applications = len(pending_applications) + len(processed_applications) recent_applications = pending_applications # Active contracts only active_contracts = Contract.query.filter( Contract.contractor_id == current_user.id, Contract.status.in_(['active', 'pending_review']) ).order_by(Contract.created_at.desc()).all() # Calculate total pending payouts from active contracts pending_payouts = sum(float(contract.agreed_rate) for contract in active_contracts if contract.agreed_rate) # Get Michael's posted jobs (ADD THIS) posted_jobs = Job.query.filter_by( contractor_id=current_user.id ).order_by(Job.date_posted.desc()).all() return render_template('dashboard/contractor.html', posted_jobs=posted_jobs, recent_applications=recent_applications, processed_applications=processed_applications, pending_applications=pending_applications, active_contracts=active_contracts, pending_payouts=pending_payouts, job_count=len(posted_jobs)) @app.route('/dashboard/worker') def dashboard_worker(): """Worker dashboard""" from app.models import Contract from app.extensions import db if not current_user.is_authenticated or current_user.role != 'worker': return redirect(url_for('login')) # Get active contracts only active_contracts = Contract.query.filter( Contract.worker_id == current_user.id, Contract.status.in_(['active', 'pending_review', 'pending_rating']) ).order_by(Contract.created_at.desc()).all() # Calculate pending money from active contracts pending_money = sum(float(contract.agreed_rate) for contract in active_contracts if contract.agreed_rate) # Get average rating from completed contracts with reviews completed_contracts = Contract.query.filter( Contract.worker_id == current_user.id, Contract.status == 'completed' ).all() # Calculate average rating (assuming reviews have rating fields) total_rating = 0 rating_count = 0 for contract in completed_contracts: if hasattr(contract, 'reviews') and contract.reviews: for review in contract.reviews: if hasattr(review, 'rating') and review.rating: total_rating += review.rating rating_count += 1 avg_rating = round(total_rating / rating_count, 1) if rating_count > 0 else 0.0 # Count active jobs (contracts that need action) active_job_count = len(active_contracts) return render_template('dashboard/worker.html', active_contracts=active_contracts, pending_money=pending_money, avg_rating=avg_rating, active_job_count=active_job_count) @app.route('/profile') def profile(): """User profile page""" if not current_user.is_authenticated: flash('Please login to access your profile', 'warning') return redirect(url_for('login')) return render_template('auth/profile.html') @app.route('/health') def app_health(): """Simple health check for the web application""" return { "status": "healthy", "message": "RateRight web application is running", "timestamp": datetime.utcnow().isoformat() } # API info is handled in __init__.py @app.route('/applications//respond', methods=['GET', 'POST']) @login_required def respond_to_application(application_id): """Accept or reject job application (contractor only)""" from app.extensions import db from app.models import Application # Import Application model from app.models import Job # Import Job model if current_user.role != 'contractor': flash('Only contractors can respond to applications.', 'error') return redirect(url_for('dashboard')) application = Application.query.get_or_404(application_id) # Check if user owns the job if application.job.contractor_id != current_user.id: flash('You can only respond to applications for your own jobs.', 'error') return redirect(url_for('dashboard')) if request.method == 'GET': # Show application details for review return render_template('applications/respond.html', application=application) action = request.form.get('action') if action == 'accept': application.status = 'accepted' # Close the job and reject other applications application.job.status = 'assigned' # Reject all other pending applications for this job other_applications = Application.query.filter_by( job_id=application.job_id, status='pending' ).filter(Application.id != application_id).all() for other_app in other_applications: other_app.status = 'rejected' # Create Contract record from app.models import Contract from datetime import date, timedelta # Calculate end date (estimate 30 days from start) start_date = date.today() end_date = start_date + timedelta(days=30) # Debug rate transfer proposed_rate = application.proposed_rate fallback_rate = application.job.budget_max or application.job.hourly_rate or 0 final_rate = proposed_rate if proposed_rate is not None else fallback_rate print(f"💰 Contract Rate Debug:") print(f" Application proposed_rate: {proposed_rate}") print(f" Job budget_max: {application.job.budget_max}") print(f" Job hourly_rate: {application.job.hourly_rate}") print(f" Final agreed_rate: {final_rate}") # Determine if this is hourly or total rate based on job data rate_type = 'hourly' if application.job.hourly_rate else 'total' # Create contract with proper workflow status contract = Contract( job_id=application.job_id, contractor_id=application.job.contractor_id, worker_id=application.worker_id, agreed_rate=final_rate, rate_type=rate_type, # Set correct rate type start_date=start_date, end_date=end_date, scope_of_work=application.job.description or "Contract work as per job posting", status='pending_agreement' # Correct! Requires both parties to agree ) # Add and commit contract with proper error handling db.session.add(contract) try: db.session.commit() print(f"✅ Contract created successfully: ID {contract.id}") # Verify what was actually saved to database saved_contract = Contract.query.get(contract.id) print(f"🔍 VERIFICATION - Saved contract agreed_rate: {saved_contract.agreed_rate}") print(f"🔍 VERIFICATION - Saved contract type: {type(saved_contract.agreed_rate)}") # Verify contract was created with valid ID if contract.id: flash(f'Application accepted! Contract #{contract.id} created and ready for review.', 'success') return redirect(url_for('contracts_review', contract_id=contract.id)) else: flash('Contract created but ID assignment failed. Check your dashboard.', 'warning') return redirect(url_for('dashboard_contractor')) except Exception as e: db.session.rollback() flash(f'Error creating contract: {str(e)}', 'error') print(f"Contract creation error: {e}") # For debugging return redirect(url_for('dashboard_contractor')) elif action == 'reject': application.status = 'rejected' flash(f'Application from {application.worker.first_name} {application.worker.last_name} rejected.', 'info') try: db.session.commit() except Exception as e: db.session.rollback() flash('Error updating application. Please try again.', 'error') return redirect(url_for('dashboard_contractor')) @app.route('/contracts//review', methods=['GET', 'POST']) @login_required def contracts_review(contract_id): """Review contract details and sign""" from app.models import Contract from app.extensions import db contract = Contract.query.get_or_404(contract_id) # Check if user is party to this contract if current_user.id not in [contract.contractor_id, contract.worker_id]: flash('You do not have access to this contract.', 'error') return redirect(url_for('dashboard')) if request.method == 'POST': action = request.form.get('action') if action == 'contractor_review' and current_user.id == contract.contractor_id: contract.contractor_reviewed = True flash('Contract marked as reviewed by contractor.', 'success') elif action == 'worker_review' and current_user.id == contract.worker_id: contract.worker_reviewed = True flash('Contract marked as reviewed by worker.', 'success') else: flash('Invalid action or insufficient permissions.', 'error') return redirect(url_for('contracts_review', contract_id=contract_id)) db.session.commit() return redirect(url_for('contracts_review', contract_id=contract_id)) return render_template('contracts/review.html', contract=contract) @app.route('/contracts//sign', methods=['GET', 'POST']) @login_required def contracts_sign(contract_id): """Sign a contract - GET shows form, POST processes signing""" from app.models import Contract from app.extensions import db contract = Contract.query.get_or_404(contract_id) if not contract.can_sign(current_user.id): flash('You cannot sign this contract at this time.', 'error') return redirect(url_for('contracts_review', contract_id=contract_id)) # Auto-mark as reviewed when signing if current_user.id == contract.contractor_id: contract.contractor_reviewed = True elif current_user.id == contract.worker_id: contract.worker_reviewed = True if request.method == 'POST': success = contract.sign_contract(current_user.id) if success: db.session.commit() if contract.status == 'active': flash('Contract fully executed! Work can now begin.', 'success') # TODO: Send notifications to both parties else: flash('Contract signed successfully. Waiting for other party.', 'success') else: flash('Failed to sign contract.', 'error') return redirect(url_for('contracts_review', contract_id=contract_id)) # GET request - show signing confirmation page return render_template('contracts/review.html', contract=contract, show_sign_form=True) @app.route('/contracts//mark-complete', methods=['POST']) @login_required def mark_contract_complete(contract_id): """Worker marks contract work as complete""" from app.models import Contract from app.extensions import db contract = Contract.query.get_or_404(contract_id) # Only worker can mark as complete if contract.worker_id != current_user.id: flash('Only the worker can mark work as complete.', 'error') return redirect(url_for('contract_closeout', contract_id=contract_id)) if contract.status != 'active': flash('Contract must be active to mark complete.', 'error') return redirect(url_for('contract_closeout', contract_id=contract_id)) # Update contract status contract.status = 'pending_review' # Find associated payment if exists payment = contract.payments.filter_by(status='held_escrow').first() if payment: payment.status = 'pending_release' payment.release_conditions_met = True db.session.commit() flash('Work marked as complete - awaiting contractor review.', 'success') return redirect(url_for('contract_closeout', contract_id=contract_id)) @app.route('/contracts//approve-completion', methods=['POST']) @login_required def approve_completion(contract_id): """Contractor approves work completion""" from app.models.contract import Contract from app.extensions import db from datetime import datetime contract = Contract.query.get_or_404(contract_id) # Only contractor can approve if current_user.id != contract.contractor_id: flash('Only the contractor can approve completion.', 'error') return redirect(url_for('contracts_review', contract_id=contract_id)) # Update status contract.status = 'pending_rating' contract.contractor_approval_date = datetime.utcnow() db.session.commit() flash('Work approved! You can now rate each other.', 'success') return redirect(url_for('contracts_review', contract_id=contract_id)) @app.route('/contracts//dispute-completion', methods=['POST']) @login_required def dispute_completion(contract_id): """Contractor disputes work completion""" from app.models.contract import Contract from app.extensions import db contract = Contract.query.get_or_404(contract_id) if current_user.id != contract.contractor_id: flash('Only the contractor can dispute completion.', 'error') return redirect(url_for('contracts_review', contract_id=contract_id)) contract.status = 'disputed' db.session.commit() flash('Completion disputed. Please contact the worker to resolve.', 'warning') return redirect(url_for('contracts_review', contract_id=contract_id)) @app.route('/contracts', methods=['GET']) @login_required def contracts_list(): """List all contracts for current user""" from app.models import Contract if current_user.role == 'contractor': contracts = Contract.query.filter_by(contractor_id=current_user.id).order_by(Contract.created_at.desc()).all() else: contracts = Contract.query.filter_by(worker_id=current_user.id).order_by(Contract.created_at.desc()).all() return render_template('contracts/list.html', contracts=contracts) @app.route('/applications//accept', methods=['POST']) @login_required def accept_application(application_id): """Accept a job application and create contract""" from app.models import Application, Contract from datetime import date, timedelta if not current_user.is_authenticated or current_user.role != 'contractor': flash('Only contractors can accept applications', 'error') return redirect(url_for('login')) application = Application.query.get_or_404(application_id) # Check if contractor owns this job if application.job.contractor_id != current_user.id: flash('You can only accept applications for your own jobs', 'error') return redirect(url_for('applications_contractor_list')) if application.status != 'pending': flash('Application has already been processed', 'warning') return redirect(url_for('applications_contractor_list')) try: # Accept the application application.status = 'accepted' # Close the job application.job.status = 'assigned' # Create contract automatically start_date = date.today() + timedelta(days=7) # Start in 1 week end_date = start_date + timedelta(days=30) # 30 day contract contract = Contract( job_id=application.job_id, contractor_id=current_user.id, worker_id=application.worker_id, agreed_rate=application.proposed_rate if application.proposed_rate is not None else (application.job.budget_max or application.job.hourly_rate or 5000.00), rate_type='total', start_date=start_date, end_date=end_date, scope_of_work=application.job.description, payment_terms='completion', status='pending_agreement' ) db.session.add(contract) # Reject other pending applications for this job other_applications = Application.query.filter_by( job_id=application.job_id, status='pending' ).filter(Application.id != application_id).all() for other_app in other_applications: other_app.status = 'rejected' db.session.commit() flash(f'Application accepted! Contract #{contract.id} created and ready for review.', 'success') except Exception as e: db.session.rollback() flash(f'Error accepting application: {str(e)}', 'error') return redirect(url_for('applications_contractor_list')) @app.route('/contracts/') @login_required def contract_review(contract_id): """Review contract details and sign""" from app.models import Contract from app.extensions import db contract = Contract.query.get_or_404(contract_id) # Check authorization if current_user.id not in [contract.contractor_id, contract.worker_id]: flash('You are not authorized to view this contract.', 'danger') return redirect(url_for('dashboard')) return render_template('contracts/review.html', contract=contract) @app.route('/contracts//closeout') @login_required def contract_closeout(contract_id): """Contract close-out interface""" from app.models import Contract from app.extensions import db contract = Contract.query.get_or_404(contract_id) # Check authorization if current_user.id not in [contract.contractor_id, contract.worker_id]: flash('You are not authorized to view this contract.', 'danger') return redirect(url_for('dashboard')) return render_template('contracts/closeout.html', contract=contract) @app.route('/contracts//rate', methods=['GET', 'POST']) @login_required def rate_contract(contract_id): """Rate contract performance (both parties can rate)""" from app.models.contract import Contract from app.models.safety import Review from app.extensions import db contract = Contract.query.get_or_404(contract_id) # Check authorization if current_user.id not in [contract.contractor_id, contract.worker_id]: flash('You are not authorized to rate this contract.', 'error') return redirect(url_for('dashboard')) # Check if contract is ready for rating if contract.status != 'pending_rating': flash('This contract is not ready for rating.', 'warning') return redirect(url_for('contracts_review', contract_id=contract_id)) # Check if user already rated existing_review = Review.query.filter_by( contract_id=contract_id, reviewer_id=current_user.id ).first() if existing_review: flash('You have already rated this contract.', 'info') return redirect(url_for('contracts_review', contract_id=contract_id)) if request.method == 'POST': try: # Get form data overall_rating = int(request.form.get('overall_rating')) quality_rating = int(request.form.get('quality_rating')) communication_rating = int(request.form.get('communication_rating')) safety_rating = int(request.form.get('safety_rating')) comment = request.form.get('comment', '').strip() would_work_again = bool(request.form.get('would_work_again')) # Validate ratings for rating in [overall_rating, quality_rating, communication_rating, safety_rating]: if not (1 <= rating <= 5): flash('All ratings must be between 1 and 5.', 'error') return render_template('contracts/rate.html', contract=contract) # Determine who is being rated reviewee_id = contract.worker_id if current_user.id == contract.contractor_id else contract.contractor_id # Create review review = Review( reviewer_id=current_user.id, reviewee_id=reviewee_id, job_id=contract.job_id, contract_id=contract_id, overall_rating=overall_rating, quality_rating=quality_rating, communication_rating=communication_rating, safety_rating=safety_rating, comment=comment, would_work_again=would_work_again, verified_completion=True ) db.session.add(review) # Update contract rating status if current_user.id == contract.contractor_id: contract.contractor_rated = True else: contract.worker_rated = True # Check if both parties have rated if contract.contractor_rated and contract.worker_rated: contract.status = 'completed' contract.mutual_rating_completed_date = datetime.utcnow() flash('Rating submitted! Contract is now complete.', 'success') else: flash('Rating submitted! Waiting for the other party to rate.', 'success') db.session.commit() return redirect(url_for('contracts_review', contract_id=contract_id)) except (ValueError, TypeError) as e: flash('Invalid rating values provided.', 'error') return render_template('contracts/rate.html', contract=contract) except Exception as e: db.session.rollback() flash(f'Error submitting rating: {str(e)}', 'error') return render_template('contracts/rate.html', contract=contract) # GET request - show rating form return render_template('contracts/rate.html', contract=contract) @app.route('/contracts//ratings') @login_required def view_ratings(contract_id): """View all ratings for a contract""" from app.models.contract import Contract from app.models.safety import Review contract = Contract.query.get_or_404(contract_id) # Authorization check if current_user.id not in [contract.contractor_id, contract.worker_id]: flash('Not authorized to view these ratings.', 'error') return redirect(url_for('dashboard')) # Get all reviews for this contract reviews = Review.query.filter_by(contract_id=contract_id).all() return render_template('contracts/ratings.html', contract=contract, reviews=reviews)