# Add these endpoints to app/blueprints/marketplace/routes.py @marketplace_bp.route('/jobs', methods=['POST']) @jwt_required() def create_job(): """Create a new job (contractors only)""" try: user_id = get_jwt_identity() user = User.query.get(user_id) if not user: return jsonify({'error': 'User not found'}), 404 if user.role != 'contractor': return jsonify({'error': 'Only contractors can post jobs'}), 403 # Check contractor compliance compliance_valid, compliance_issues = user.is_compliance_valid() if not compliance_valid: return jsonify({ 'error': 'Contractor compliance required to post jobs', 'issues': compliance_issues }), 400 data = request.get_json() # Validate required fields required_fields = ['title', 'description', 'category_id', 'location'] for field in required_fields: if not data.get(field): return jsonify({'error': f'{field} is required'}), 400 # Validate category exists category = Category.query.get(data['category_id']) if not category or not category.is_active: return jsonify({'error': 'Invalid or inactive category'}), 400 # Create job job = Job( title=data['title'], description=data['description'], contractor_id=user_id, category_id=data['category_id'], location=data['location'], budget_min=data.get('budget_min'), budget_max=data.get('budget_max'), whs_requirements=data.get('whs_requirements') or f"Category: {category.name}. Risk Level: {category.whs_risk_level}. {category.insurance_requirements or ''}", insurance_required=data.get('insurance_required', True), white_card_required=data.get('white_card_required', category.white_card_required), deadline=datetime.fromisoformat(data['deadline']) if data.get('deadline') else None ) db.session.add(job) db.session.commit() # Award points for posting job from ...utils.gamification import award_points award_points(user_id, 'job_posted', 50) return jsonify({ 'message': 'Job posted successfully', 'job_id': job.id, 'job': { 'id': job.id, 'title': job.title, 'location': job.location, 'status': job.status, 'date_posted': job.date_posted.isoformat() } }), 201 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @marketplace_bp.route('/jobs/', methods=['PUT']) @jwt_required() def update_job(job_id): """Update job details (job owner only)""" try: user_id = get_jwt_identity() job = Job.query.get_or_404(job_id) # Check ownership if job.contractor_id != user_id: return jsonify({'error': 'You can only update your own jobs'}), 403 # Don't allow updates if job has applications if job.applications_count > 0: return jsonify({'error': 'Cannot update job with existing applications'}), 400 data = request.get_json() # Update allowed fields updatable_fields = ['title', 'description', 'location', 'budget_min', 'budget_max', 'whs_requirements', 'deadline'] for field in updatable_fields: if field in data: if field == 'deadline' and data[field]: setattr(job, field, datetime.fromisoformat(data[field])) else: setattr(job, field, data[field]) db.session.commit() return jsonify({ 'message': 'Job updated successfully', 'job': { 'id': job.id, 'title': job.title, 'description': job.description, 'location': job.location, 'budget_min': float(job.budget_min) if job.budget_min else None, 'budget_max': float(job.budget_max) if job.budget_max else None, 'status': job.status } }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @marketplace_bp.route('/my-jobs', methods=['GET']) @jwt_required() def get_my_jobs(): """Get contractor's posted jobs""" try: user_id = get_jwt_identity() user = User.query.get(user_id) if not user or user.role != 'contractor': return jsonify({'error': 'Only contractors can view posted jobs'}), 403 status = request.args.get('status', 'all') page = request.args.get('page', 1, type=int) per_page = min(request.args.get('per_page', 10, type=int), 100) # Build query query = Job.query.filter_by(contractor_id=user_id) if status != 'all': query = query.filter_by(status=status) query = query.order_by(Job.date_posted.desc()) # Paginate jobs = query.paginate(page=page, per_page=per_page, error_out=False) return jsonify({ 'jobs': [{ 'id': job.id, 'title': job.title, 'location': job.location, 'status': job.status, 'budget_min': float(job.budget_min) if job.budget_min else None, 'budget_max': float(job.budget_max) if job.budget_max else None, 'applications_count': job.applications_count, 'date_posted': job.date_posted.isoformat(), 'deadline': job.deadline.isoformat() if job.deadline else None, 'category': job.category.name } for job in jobs.items], 'pagination': { 'page': jobs.page, 'per_page': jobs.per_page, 'total': jobs.total, 'pages': jobs.pages } }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @marketplace_bp.route('/jobs//applications', methods=['GET']) @jwt_required() def get_job_applications(job_id): """Get applications for a specific job (job owner only)""" try: user_id = get_jwt_identity() job = Job.query.get_or_404(job_id) # Check ownership if job.contractor_id != user_id: return jsonify({'error': 'You can only view applications for your own jobs'}), 403 applications = Application.query.filter_by(job_id=job_id).order_by(Application.date_applied.desc()).all() return jsonify({ 'job': { 'id': job.id, 'title': job.title, 'applications_count': job.applications_count }, 'applications': [{ 'id': app.id, 'worker': { 'id': app.worker.id, 'name': f"{app.worker.first_name} {app.worker.last_name}", 'primary_trade': app.worker.primary_trade, 'average_rating': float(app.worker.average_rating) if app.worker.average_rating else 0.0, 'jobs_completed': app.worker.jobs_completed, 'location': app.worker.location }, 'status': app.status, 'proposed_rate': float(app.proposed_rate) if app.proposed_rate else None, 'cover_letter': app.cover_letter, 'date_applied': app.date_applied.isoformat(), 'abn_verified': app.abn_verified, 'insurance_verified': app.insurance_verified, 'compliance_valid': app.worker.is_compliance_valid()[0] } for app in applications] }), 200 except Exception as e: return jsonify({'error': str(e)}), 500 @marketplace_bp.route('/applications/', methods=['PUT']) @jwt_required() def update_application_status(application_id): """Accept or reject job application (job owner only)""" try: user_id = get_jwt_identity() application = Application.query.get_or_404(application_id) # Check if user owns the job if application.job.contractor_id != user_id: return jsonify({'error': 'You can only manage applications for your own jobs'}), 403 data = request.get_json() new_status = data.get('status') if new_status not in ['accepted', 'rejected']: return jsonify({'error': 'Status must be "accepted" or "rejected"'}), 400 if application.status != 'pending': return jsonify({'error': 'Can only update pending applications'}), 400 application.status = new_status # If accepted, close the job and reject other applications if new_status == 'accepted': 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' # Award points to both contractor and worker from ...utils.gamification import award_points award_points(application.job.contractor_id, 'application_accepted', 25) award_points(application.worker_id, 'application_accepted', 75) db.session.commit() return jsonify({ 'message': f'Application {new_status} successfully', 'application': { 'id': application.id, 'status': application.status, 'worker_name': f"{application.worker.first_name} {application.worker.last_name}", 'job_title': application.job.title } }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500 @marketplace_bp.route('/jobs//close', methods=['PUT']) @jwt_required() def close_job(job_id): """Close a job posting (job owner only)""" try: user_id = get_jwt_identity() job = Job.query.get_or_404(job_id) # Check ownership if job.contractor_id != user_id: return jsonify({'error': 'You can only close your own jobs'}), 403 if job.status not in ['open', 'assigned']: return jsonify({'error': 'Job is already closed'}), 400 job.status = 'closed' # Reject all pending applications pending_applications = Application.query.filter_by( job_id=job_id, status='pending' ).all() for app in pending_applications: app.status = 'rejected' db.session.commit() return jsonify({ 'message': 'Job closed successfully', 'job': { 'id': job.id, 'title': job.title, 'status': job.status } }), 200 except Exception as e: db.session.rollback() return jsonify({'error': str(e)}), 500