"""退费管理路由""" from flask import render_template, request, redirect, url_for, flash, session, jsonify from utils import permission_required, export_excel, login_required, log_operation from models import db, Student, Course, StudentAccount, RefundRecord import business_rules as rules def register_routes(app): @app.route('/refunds', endpoint='refund_list') @login_required @permission_required('refund_view') def refund_list(): records = RefundRecord.query.order_by(RefundRecord.created_at.desc()).all() return render_template('refund_list.html', records=records) @app.route('/refunds/add', methods=['GET', 'POST'], endpoint='refund_add') @login_required @permission_required('refund_add') def refund_add(): if request.method == 'POST': student_id = int(request.form.get('student_id')) course_id = int(request.form.get('course_id')) reason = request.form.get('reason', '') partial_raw = request.form.get('partial_amount', '').strip() partial_amount = float(partial_raw) if partial_raw else None account = rules.lock_student_account(student_id, course_id) if not account: flash('未找到该学员的课时账户', 'danger') return redirect(url_for('refund_add')) before = rules.account_snapshot_dict(account) calc = rules.calculate_refund(student_id, course_id, partial_amount) record = RefundRecord( student_id=student_id, course_id=course_id, refund_amount=calc['refund_amount'], remaining_normal_hours=calc['remaining_normal'], remaining_gifted_hours=calc['remaining_gifted'], deduct_gifted_hours=calc['deduct_gifted_amount'], deduct_gifted_amount=calc['deduct_gifted_amount'], promo_clawback_hours=calc['promo_clawback_hours'], consumed_hours_value=calc['consumed_hours_value'], material_fee_per_quarter=rules.MATERIAL_FEE_PER_QUARTER, quarters=calc['quarters'], total_deduct=calc['total_deduct'], reason=reason, operator_id=session.get('user_id'), ) try: db.session.add(record) rules.apply_refund( account, calc, student_id=student_id, course_id=course_id, ) after = rules.account_snapshot_dict(account) log_operation( '退费', f'学员ID:{student_id} 退费{calc["refund_amount"]}元', before=before, after=after, commit=False, ) db.session.commit() except Exception as e: db.session.rollback() flash(f'退费失败: {e}', 'danger') return redirect(url_for('refund_add')) extra = '' if calc['promo_clawback_hours']: extra = f',优惠回退扣赠课{calc["promo_clawback_hours"]}课时' flash(f'退费成功!退费金额: {calc["refund_amount"]}元{extra}', 'success') return redirect(url_for('refund_list')) students = Student.query.filter_by(status=1).all() courses = Course.query.filter_by(status=1).all() return render_template('refund_form.html', students=students, courses=courses) @app.route('/refunds/export', endpoint='refund_export') @login_required @permission_required('refund_export') def refund_export(): records = RefundRecord.query.order_by(RefundRecord.created_at.desc()).all() headers = ['序号', '学员姓名', '课程名称', '退费金额', '已消课时金额', '材料费', '赠课扣回金额', '优惠回退课时', '退费原因', '创建时间'] rows = [] for i, r in enumerate(records, 1): student = db.session.get(Student, r.student_id) course = db.session.get(Course, r.course_id) gift_amt = getattr(r, 'deduct_gifted_amount', None) or r.deduct_gifted_hours rows.append([ i, student.name if student else '', course.name if course else '', r.refund_amount or 0, r.consumed_hours_value or 0, (r.quarters or 0) * rules.MATERIAL_FEE_PER_QUARTER, gift_amt or 0, getattr(r, 'promo_clawback_hours', 0) or 0, r.reason or '', r.created_at.strftime('%Y-%m-%d %H:%M') if r.created_at else '', ]) return export_excel('退费记录', headers, rows) @app.route('/api/calculate_refund', methods=['POST']) @login_required @permission_required('refund_view') def api_calculate_refund(): data = request.get_json() or {} student_id = data.get('student_id') course_id = data.get('course_id') partial = data.get('partial_amount') partial_amount = float(partial) if partial not in (None, '') else None result = rules.calculate_refund(student_id, course_id, partial_amount) return jsonify({'success': True, 'data': result})