from flask import Blueprint, request, redirect, url_for, flash, render_template, jsonify from flask_login import current_user, login_required from app.models.book import Book from app.models.borrow import BorrowRecord from app.models.inventory import InventoryLog from app.models.user import db, User from app.models.log import Log # 导入日志模型 import datetime from app.utils.auth import permission_required # 修改导入,使用permission_required而不是admin_required # 创建借阅蓝图 borrow_bp = Blueprint('borrow', __name__, url_prefix='/borrow') @borrow_bp.route('/book', methods=['POST']) @login_required def borrow_book(): book_id = request.form.get('book_id', type=int) borrow_days = request.form.get('borrow_days', type=int, default=14) if not book_id: flash('请选择要借阅的图书', 'danger') return redirect(url_for('book.book_list')) book = Book.query.get_or_404(book_id) # 检查库存 if book.stock <= 0: flash(f'《{book.title}》当前无库存,无法借阅', 'danger') return redirect(url_for('book.book_detail', book_id=book_id)) # 检查当前用户是否已借阅此书 existing_borrow = BorrowRecord.query.filter_by( user_id=current_user.id, book_id=book_id, status=1 # 1表示借阅中 ).first() if existing_borrow: flash(f'您已借阅《{book.title}》,请勿重复借阅', 'warning') return redirect(url_for('book.book_detail', book_id=book_id)) try: # 创建借阅记录 now = datetime.datetime.now() due_date = now + datetime.timedelta(days=borrow_days) borrow_record = BorrowRecord( user_id=current_user.id, book_id=book_id, borrow_date=now, due_date=due_date, status=1, # 1表示借阅中 created_at=now, updated_at=now ) # 更新图书库存 book.stock -= 1 book.updated_at = now db.session.add(borrow_record) db.session.commit() # 添加库存变更日志 inventory_log = InventoryLog( book_id=book_id, change_type='借出', change_amount=-1, after_stock=book.stock, operator_id=current_user.id, remark='用户借书', changed_at=now ) db.session.add(inventory_log) # 添加系统操作日志 Log.add_log( action='借阅图书', user_id=current_user.id, target_type='book', target_id=book_id, ip_address=request.remote_addr, description=f'用户借阅图书《{book.title}》,归还日期: {due_date.strftime("%Y-%m-%d")}' ) db.session.commit() flash(f'成功借阅《{book.title}》,请在 {due_date.strftime("%Y-%m-%d")} 前归还', 'success') except Exception as e: db.session.rollback() flash(f'借阅失败: {str(e)}', 'danger') return redirect(url_for('book.book_detail', book_id=book_id)) @borrow_bp.route('/add/', methods=['POST']) @login_required def add_borrow(book_id): # 验证图书存在 book = Book.query.get_or_404(book_id) # 默认借阅天数 borrow_days = 14 # 检查库存 if book.stock <= 0: return jsonify({ 'success': False, 'message': f'《{book.title}》当前无库存,无法借阅' }) # 检查是否已借阅 existing_borrow = BorrowRecord.query.filter_by( user_id=current_user.id, book_id=book_id, status=1 # 1表示借阅中 ).first() if existing_borrow: return jsonify({ 'success': False, 'message': f'您已借阅《{book.title}》,请勿重复借阅' }) try: # 创建借阅记录 now = datetime.datetime.now() due_date = now + datetime.timedelta(days=borrow_days) borrow_record = BorrowRecord( user_id=current_user.id, book_id=book_id, borrow_date=now, due_date=due_date, status=1, # 1表示借阅中 created_at=now, updated_at=now ) # 更新图书库存 book.stock -= 1 book.updated_at = now db.session.add(borrow_record) db.session.commit() # 添加库存变更日志 inventory_log = InventoryLog( book_id=book_id, change_type='借出', change_amount=-1, after_stock=book.stock, operator_id=current_user.id, remark='用户借书', changed_at=now ) db.session.add(inventory_log) # 添加系统操作日志 Log.add_log( action='借阅图书', user_id=current_user.id, target_type='book', target_id=book_id, ip_address=request.remote_addr, description=f'用户借阅图书《{book.title}》,归还日期: {due_date.strftime("%Y-%m-%d")}' ) db.session.commit() return jsonify({ 'success': True, 'message': f'成功借阅《{book.title}》,请在 {due_date.strftime("%Y-%m-%d")} 前归还' }) except Exception as e: db.session.rollback() return jsonify({ 'success': False, 'message': f'借阅失败: {str(e)}' }) @borrow_bp.route('/return/', methods=['POST']) @login_required def return_book(borrow_id): """还书操作""" # 查找借阅记录 borrow_record = BorrowRecord.query.get_or_404(borrow_id) # 检查是否是自己的借阅记录或者是管理员 if borrow_record.user_id != current_user.id and not current_user.has_permission('manage_borrows'): return jsonify({ 'success': False, 'message': '您无权执行此操作' }) # 检查是否已还 if borrow_record.status != 1: return jsonify({ 'success': False, 'message': '此书已归还,请勿重复操作' }) try: book = Book.query.get(borrow_record.book_id) now = datetime.datetime.now() # 更新借阅记录 borrow_record.status = 0 # 0表示已归还 borrow_record.return_date = now borrow_record.updated_at = now # 更新图书库存 book.stock += 1 book.updated_at = now db.session.commit() # 添加库存变更日志 inventory_log = InventoryLog( book_id=borrow_record.book_id, change_type='归还', change_amount=1, after_stock=book.stock, operator_id=current_user.id, remark='用户还书', changed_at=now ) db.session.add(inventory_log) # 添加系统操作日志 # 判断是否逾期归还 is_overdue = now > borrow_record.due_date overdue_msg = '(逾期归还)' if is_overdue else '' Log.add_log( action='归还图书', user_id=current_user.id, target_type='book', target_id=borrow_record.book_id, ip_address=request.remote_addr, description=f'用户归还图书《{book.title}》{overdue_msg}' ) db.session.commit() return jsonify({ 'success': True, 'message': f'成功归还《{book.title}》' }) except Exception as e: db.session.rollback() return jsonify({ 'success': False, 'message': f'归还失败: {str(e)}' }) @borrow_bp.route('/renew/', methods=['POST']) @login_required def renew_book(borrow_id): """续借操作""" # 查找借阅记录 borrow_record = BorrowRecord.query.get_or_404(borrow_id) # 检查是否是自己的借阅记录或者是管理员 if borrow_record.user_id != current_user.id and not current_user.has_permission('manage_borrows'): return jsonify({ 'success': False, 'message': '您无权执行此操作' }) # 检查是否已还 if borrow_record.status != 1: return jsonify({ 'success': False, 'message': '此书已归还,无法续借' }) # 检查续借次数限制(最多续借2次) if borrow_record.renew_count >= 2: return jsonify({ 'success': False, 'message': '此书已达到最大续借次数,无法继续续借' }) try: now = datetime.datetime.now() book = Book.query.get(borrow_record.book_id) # 检查是否已逾期 if now > borrow_record.due_date: return jsonify({ 'success': False, 'message': '此书已逾期,请先归还并处理逾期情况' }) # 续借14天 new_due_date = borrow_record.due_date + datetime.timedelta(days=14) # 更新借阅记录 borrow_record.due_date = new_due_date borrow_record.renew_count += 1 borrow_record.updated_at = now # 添加系统操作日志 Log.add_log( action='续借图书', user_id=current_user.id, target_type='book', target_id=borrow_record.book_id, ip_address=request.remote_addr, description=f'用户续借图书《{book.title}》,新归还日期: {new_due_date.strftime("%Y-%m-%d")}' ) db.session.commit() return jsonify({ 'success': True, 'message': f'续借成功,新的归还日期为 {new_due_date.strftime("%Y-%m-%d")}' }) except Exception as e: db.session.rollback() return jsonify({ 'success': False, 'message': f'续借失败: {str(e)}' }) @borrow_bp.route('/my_borrows') @login_required def my_borrows(): """用户查看自己的借阅记录""" page = request.args.get('page', 1, type=int) status = request.args.get('status', default=None, type=int) # 构建查询 query = BorrowRecord.query.filter_by(user_id=current_user.id) # 根据状态筛选 if status is not None: query = query.filter_by(status=status) # 按借阅日期倒序排列 query = query.order_by(BorrowRecord.borrow_date.desc()) # 分页 pagination = query.paginate(page=page, per_page=10, error_out=False) # 获取当前借阅数量和历史借阅数量(用于标签显示) current_borrows_count = BorrowRecord.query.filter_by(user_id=current_user.id, status=1).count() history_borrows_count = BorrowRecord.query.filter_by(user_id=current_user.id, status=0).count() # 记录日志 - 用户查看借阅记录 Log.add_log( action='查看借阅记录', user_id=current_user.id, ip_address=request.remote_addr, description='用户查看个人借阅记录' ) return render_template( 'borrow/my_borrows.html', pagination=pagination, current_borrows_count=current_borrows_count, history_borrows_count=history_borrows_count, status=status, now=datetime.datetime.now() # 添加当前时间变量 ) @borrow_bp.route('/manage') @login_required @permission_required('manage_borrows') # 替代 @admin_required def manage_borrows(): """管理员查看所有借阅记录""" page = request.args.get('page', 1, type=int) status = request.args.get('status', default=None, type=int) user_id = request.args.get('user_id', default=None, type=int) book_id = request.args.get('book_id', default=None, type=int) search = request.args.get('search', default='') # 构建查询 query = BorrowRecord.query # 根据状态筛选 if status is not None: query = query.filter_by(status=status) # 根据用户筛选 if user_id: query = query.filter_by(user_id=user_id) # 根据图书筛选 if book_id: query = query.filter_by(book_id=book_id) # 根据搜索条件筛选(用户名或图书名) if search: query = query.join(User, BorrowRecord.user_id == User.id) \ .join(Book, BorrowRecord.book_id == Book.id) \ .filter((User.username.like(f'%{search}%')) | (Book.title.like(f'%{search}%'))) # 按借阅日期倒序排列 query = query.order_by(BorrowRecord.borrow_date.desc()) # 分页 pagination = query.paginate(page=page, per_page=10, error_out=False) # 获取统计数据 current_borrows_count = BorrowRecord.query.filter_by(status=1).count() history_borrows_count = BorrowRecord.query.filter_by(status=0).count() # 获取所有用户(用于筛选) users = User.query.all() # 记录日志 - 管理员查看借阅记录 Log.add_log( action='管理借阅记录', user_id=current_user.id, ip_address=request.remote_addr, description='管理员查看借阅管理页面' ) return render_template( 'borrow/borrow_management.html', pagination=pagination, current_borrows_count=current_borrows_count, history_borrows_count=history_borrows_count, status=status, user_id=user_id, book_id=book_id, search=search, users=users, now=datetime.datetime.now() # 添加当前时间变量 ) @borrow_bp.route('/admin/add', methods=['POST']) @login_required @permission_required('manage_borrows') # 替代 @admin_required def admin_add_borrow(): """管理员为用户添加借阅记录""" user_id = request.form.get('user_id', type=int) book_id = request.form.get('book_id', type=int) borrow_days = request.form.get('borrow_days', type=int, default=14) if not user_id or not book_id: flash('用户ID和图书ID不能为空', 'danger') return redirect(url_for('borrow.manage_borrows')) # 验证用户和图书是否存在 user = User.query.get_or_404(user_id) book = Book.query.get_or_404(book_id) # 检查库存 if book.stock <= 0: flash(f'《{book.title}》当前无库存,无法借阅', 'danger') return redirect(url_for('borrow.manage_borrows')) # 检查用户是否已借阅此书 existing_borrow = BorrowRecord.query.filter_by( user_id=user_id, book_id=book_id, status=1 # 1表示借阅中 ).first() if existing_borrow: flash(f'用户 {user.username} 已借阅《{book.title}》,请勿重复借阅', 'warning') return redirect(url_for('borrow.manage_borrows')) try: # 创建借阅记录 now = datetime.datetime.now() due_date = now + datetime.timedelta(days=borrow_days) borrow_record = BorrowRecord( user_id=user_id, book_id=book_id, borrow_date=now, due_date=due_date, status=1, # 1表示借阅中 created_at=now, updated_at=now ) # 更新图书库存 book.stock -= 1 book.updated_at = now db.session.add(borrow_record) db.session.commit() # 添加库存变更日志 inventory_log = InventoryLog( book_id=book_id, change_type='借出', change_amount=-1, after_stock=book.stock, operator_id=current_user.id, remark=f'管理员 {current_user.username} 为用户 {user.username} 借书', changed_at=now ) db.session.add(inventory_log) # 添加系统操作日志 Log.add_log( action='管理员借阅操作', user_id=current_user.id, target_type='book', target_id=book_id, ip_address=request.remote_addr, description=f'管理员为用户 {user.username} 借阅图书《{book.title}》,归还日期: {due_date.strftime("%Y-%m-%d")}' ) db.session.commit() flash(f'成功为用户 {user.username} 借阅《{book.title}》,归还日期: {due_date.strftime("%Y-%m-%d")}', 'success') except Exception as e: db.session.rollback() flash(f'借阅失败: {str(e)}', 'danger') return redirect(url_for('borrow.manage_borrows')) @borrow_bp.route('/overdue') @login_required @permission_required('manage_overdue') # 替代 @admin_required def overdue_borrows(): """查看逾期借阅""" page = request.args.get('page', 1, type=int) now = datetime.datetime.now() # 查询所有已逾期且未归还的借阅记录 query = BorrowRecord.query.filter( BorrowRecord.status == 1, # 借阅中 BorrowRecord.due_date < now # 已过期 ).order_by(BorrowRecord.due_date) # 按到期日期排序,最早到期的排在前面 pagination = query.paginate(page=page, per_page=10, error_out=False) # 计算逾期总数 overdue_count = query.count() # 记录日志 - 管理员查看逾期记录 Log.add_log( action='查看逾期记录', user_id=current_user.id, ip_address=request.remote_addr, description='管理员查看逾期借阅记录' ) return render_template( 'borrow/overdue.html', pagination=pagination, overdue_count=overdue_count ) @borrow_bp.route('/overdue/notify/', methods=['POST']) @login_required @permission_required('manage_overdue') # 替代 @admin_required def notify_overdue(borrow_id): """发送逾期通知""" from app.models.notification import Notification borrow_record = BorrowRecord.query.get_or_404(borrow_id) # 检查是否已还 if borrow_record.status != 1: return jsonify({ 'success': False, 'message': '此书已归还,无需发送逾期通知' }) now = datetime.datetime.now() # 检查是否确实逾期 if borrow_record.due_date > now: return jsonify({ 'success': False, 'message': '此借阅记录尚未逾期' }) try: book = Book.query.get(borrow_record.book_id) user = User.query.get(borrow_record.user_id) # 创建通知 notification = Notification( user_id=borrow_record.user_id, title='图书逾期提醒', content=f'您借阅的《{borrow_record.book.title}》已逾期,请尽快归还。应还日期: {borrow_record.due_date.strftime("%Y-%m-%d")}', type='overdue', sender_id=current_user.id, created_at=now ) db.session.add(notification) # 更新借阅记录备注 borrow_record.remark = f'{borrow_record.remark or ""}[{now.strftime("%Y-%m-%d")} 已发送逾期通知]' borrow_record.updated_at = now # 添加系统操作日志 Log.add_log( action='发送逾期通知', user_id=current_user.id, target_type='notification', target_id=borrow_record.user_id, ip_address=request.remote_addr, description=f'管理员向用户 {user.username} 发送图书《{book.title}》逾期通知' ) db.session.commit() return jsonify({ 'success': True, 'message': '已成功发送逾期通知' }) except Exception as e: db.session.rollback() return jsonify({ 'success': False, 'message': f'发送通知失败: {str(e)}' })