superlishunqin 29914a4178 0506
2025-05-06 12:01:11 +08:00

540 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
import datetime
from app.utils.auth import 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)
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/<int:book_id>', 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)
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/<int:borrow_id>', 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 current_user.role_id != 1:
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)
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/<int:borrow_id>', 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 current_user.role_id != 1:
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()
# 检查是否已逾期
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
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()
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
@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()
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
@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)
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
@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()
return render_template(
'borrow/overdue.html',
pagination=pagination,
overdue_count=overdue_count
)
@borrow_bp.route('/overdue/notify/<int:borrow_id>', methods=['POST'])
@login_required
@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:
# 创建通知
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)
db.session.commit()
# 更新借阅记录备注
borrow_record.remark = f'{borrow_record.remark or ""}[{now.strftime("%Y-%m-%d")} 已发送逾期通知]'
borrow_record.updated_at = now
db.session.commit()
return jsonify({
'success': True,
'message': '已成功发送逾期通知'
})
except Exception as e:
db.session.rollback()
return jsonify({
'success': False,
'message': f'发送通知失败: {str(e)}'
})