feat: 实现单价分离、班级转课、消课筛选和手动录入功能

Coze-Commit-Type: user
Coze-User-ID: 3722323274763196
Coze-Conversation-ID: 5260473
This commit is contained in:
user9994793890
2026-05-29 10:40:41 +08:00
parent ee860ce0ae
commit 3468394c6c
10 changed files with 727 additions and 66 deletions

View File

@@ -5,7 +5,7 @@ from utils import (
permission_required, export_excel, login_required, log_operation, parse_date,
filter_students_query, teacher_can_access_class, teacher_can_access_student,
)
from models import (db, Student, Course, Class_, ClassStudent, ConsumptionRecord)
from models import (db, Student, Course, Class_, ClassStudent, Teacher, ConsumptionRecord)
import business_rules as rules
@@ -16,7 +16,14 @@ def register_routes(app):
@permission_required('consumption_view')
def consumption_list():
keyword = request.args.get('keyword', '')
class_id = request.args.get('class_id', '')
teacher_id = request.args.get('teacher_id', '')
start_date = request.args.get('start_date', '')
end_date = request.args.get('end_date', '')
query = ConsumptionRecord.query
# 按学员姓名搜索
if keyword:
sq = filter_students_query(Student.query.filter(Student.name.contains(keyword)))
student_ids = [s.id for s in sq.all()]
@@ -24,8 +31,46 @@ def register_routes(app):
query = query.filter(ConsumptionRecord.student_id.in_(student_ids))
else:
query = query.filter(False)
# 按班级筛选
if class_id:
class_students = ClassStudent.query.filter_by(class_id=int(class_id), status=1).all()
student_ids = [cs.student_id for cs in class_students]
if student_ids:
query = query.filter(ConsumptionRecord.student_id.in_(student_ids))
else:
query = query.filter(False)
# 按老师筛选
if teacher_id:
teacher_classes = Class_.query.filter_by(teacher_id=int(teacher_id), status=1).all()
class_ids = [c.id for c in teacher_classes]
class_students = ClassStudent.query.filter(
ClassStudent.class_id.in_(class_ids),
ClassStudent.status == 1
).all()
student_ids = [cs.student_id for cs in class_students]
if student_ids:
query = query.filter(ConsumptionRecord.student_id.in_(student_ids))
else:
query = query.filter(False)
# 按日期范围筛选
if start_date:
query = query.filter(ConsumptionRecord.consume_date >= start_date)
if end_date:
query = query.filter(ConsumptionRecord.consume_date <= end_date)
records = query.order_by(ConsumptionRecord.consume_date.desc()).all()
return render_template('consumption_list.html', records=records, keyword=keyword)
# 获取筛选选项
classes = Class_.query.filter_by(status=1).all()
teachers = Teacher.query.filter_by(status=1).all()
return render_template('consumption_list.html', records=records, keyword=keyword,
class_id=class_id, teacher_id=teacher_id,
start_date=start_date, end_date=end_date,
classes=classes, teachers=teachers)
@app.route('/consumptions/add', methods=['GET', 'POST'], endpoint='consumption_add')
@login_required
@@ -231,3 +276,79 @@ def register_routes(app):
r.remark or '', r.created_at.strftime('%Y-%m-%d %H:%M') if r.created_at else '',
])
return export_excel('消课记录', headers, rows)
@app.route('/api/class_students/<int:class_id>')
@login_required
def api_class_students(class_id):
"""获取班级学员列表"""
class_students = ClassStudent.query.filter_by(class_id=class_id, status=1).all()
student_ids = [cs.student_id for cs in class_students]
students = Student.query.filter(Student.id.in_(student_ids), Student.status == 1).all()
return {'success': True, 'data': [{'id': s.id, 'name': s.name} for s in students]}
@app.route('/consumptions/manual', methods=['POST'], endpoint='consumption_manual_add')
@login_required
@permission_required('consumption_add')
def consumption_manual_add():
"""手动录入消课汇总"""
consume_date = parse_date(request.form.get('consume_date')) or date.today()
student_id = int(request.form.get('student_id'))
class_id = request.form.get('class_id')
if class_id:
class_id = int(class_id)
hours = float(request.form.get('hours', 2))
consume_type = request.form.get('consume_type', 'normal')
remark = request.form.get('remark', '')
# 获取班级关联的课程
cls = db.session.get(Class_, class_id) if class_id else None
course_id = cls.course_id if cls else None
if not course_id:
flash('请选择有效的班级', 'danger')
return redirect(url_for('consumption_list'))
account = rules.lock_student_account(student_id, course_id)
if not account:
flash('未找到该学员的课时账户', 'danger')
return redirect(url_for('consumption_list'))
if account.is_stopped:
flash('该学员正在停课保号中,无法消课', 'danger')
return redirect(url_for('consumption_list'))
try:
normal_consumed, gifted_consumed, actual_type = rules.deduct_hours(
account, hours,
)
except ValueError as e:
flash(str(e), 'danger')
return redirect(url_for('consumption_list'))
course = db.session.get(Course, course_id)
unit_at = rules.account_effective_unit_price(account, course)
record = ConsumptionRecord(
student_id=student_id,
class_id=class_id,
course_id=course_id,
hours_consumed=hours,
consume_type=actual_type,
normal_consumed=normal_consumed,
gifted_consumed=gifted_consumed,
unit_price_at_consume=unit_at,
operator_id=session.get('user_id'),
consume_date=consume_date,
is_makeup=0,
is_trial=0,
remark='手动录入: ' + remark if remark else '手动录入',
)
db.session.add(record)
rules.sync_account_snapshots(account, consume_date)
log_operation(
'手动消课录入',
f'学员ID:{student_id} 手动消课{hours}课时 归属{consume_date}',
)
db.session.commit()
flash('手动录入消课成功', 'success')
return redirect(url_for('consumption_list'))