Initial commit
This commit is contained in:
326
xuexiao/routes/recharge.py
Normal file
326
xuexiao/routes/recharge.py
Normal file
@@ -0,0 +1,326 @@
|
||||
"""充值管理、充值优惠活动、API路由"""
|
||||
from datetime import datetime
|
||||
from flask import render_template, request, redirect, url_for, flash, session, jsonify
|
||||
from models import (db, Student, Course, StudentAccount, RechargeActivity,
|
||||
RechargeRecord)
|
||||
from utils import (
|
||||
login_required, log_operation, permission_required, export_excel,
|
||||
filter_students_query, is_teacher_role,
|
||||
)
|
||||
import business_rules as rules
|
||||
|
||||
|
||||
def register_routes(app):
|
||||
|
||||
@app.route('/recharge_activities', endpoint='recharge_activity_list')
|
||||
@login_required
|
||||
@permission_required('recharge_activity_view')
|
||||
def recharge_activity_list():
|
||||
keyword = request.args.get('keyword', '')
|
||||
query = RechargeActivity.query
|
||||
if keyword:
|
||||
query = query.filter(RechargeActivity.name.contains(keyword))
|
||||
activities = query.order_by(RechargeActivity.id.desc()).all()
|
||||
return render_template('recharge_activity_list.html', activities=activities, keyword=keyword)
|
||||
|
||||
@app.route('/recharge_activities/add', methods=['GET', 'POST'], endpoint='recharge_activity_add')
|
||||
@login_required
|
||||
@permission_required('recharge_activity_add')
|
||||
def recharge_activity_add():
|
||||
if request.method == 'POST':
|
||||
defaults = rules.recharge_activity_defaults()
|
||||
activity = RechargeActivity(
|
||||
name=request.form.get('name'),
|
||||
trigger_amount=float(request.form.get('trigger_amount', 0)),
|
||||
gifted_hours=float(request.form.get('gifted_hours', 0)),
|
||||
gift_type=defaults['gift_type'],
|
||||
is_cumulative=defaults['is_cumulative'],
|
||||
cumulative_window=defaults['cumulative_window'],
|
||||
cumulative_clear=defaults['cumulative_clear'],
|
||||
allow_multi_trigger=defaults['allow_multi_trigger'],
|
||||
is_time_limited=int(request.form.get('is_time_limited', 0)),
|
||||
start_date=_parse_date(request.form.get('start_date')),
|
||||
end_date=_parse_date(request.form.get('end_date')),
|
||||
target_audience=defaults['target_audience'],
|
||||
can_stack=int(request.form.get('can_stack', 1)),
|
||||
gift_cap=defaults['gift_cap'],
|
||||
group_discount=defaults['group_discount'],
|
||||
group_min_people=defaults['group_min_people'],
|
||||
group_extra_hours=defaults['group_extra_hours'],
|
||||
referral_reward=defaults['referral_reward'],
|
||||
referral_reward_hours=defaults['referral_reward_hours'],
|
||||
reward_timing='immediate',
|
||||
status=1,
|
||||
)
|
||||
db.session.add(activity)
|
||||
db.session.commit()
|
||||
log_operation('创建充值活动', f'活动:{activity.name}')
|
||||
flash('充值活动创建成功', 'success')
|
||||
return redirect(url_for('recharge_activity_list'))
|
||||
|
||||
return render_template('recharge_activity_form.html', activity=None)
|
||||
|
||||
@app.route('/recharge_activities/edit/<int:id>', methods=['GET', 'POST'], endpoint='recharge_activity_edit')
|
||||
@login_required
|
||||
@permission_required('recharge_activity_edit')
|
||||
def recharge_activity_edit(id):
|
||||
activity = db.session.get(RechargeActivity, id)
|
||||
if request.method == 'POST':
|
||||
activity.name = request.form.get('name')
|
||||
activity.trigger_amount = float(request.form.get('trigger_amount', 0))
|
||||
activity.gifted_hours = float(request.form.get('gifted_hours', 0))
|
||||
activity.is_time_limited = int(request.form.get('is_time_limited', 0))
|
||||
activity.start_date = _parse_date(request.form.get('start_date'))
|
||||
activity.end_date = _parse_date(request.form.get('end_date'))
|
||||
activity.can_stack = int(request.form.get('can_stack', 1))
|
||||
activity.status = int(request.form.get('status', 1))
|
||||
db.session.commit()
|
||||
log_operation('编辑充值活动', f'活动ID:{id}')
|
||||
flash('充值活动更新成功', 'success')
|
||||
return redirect(url_for('recharge_activity_list'))
|
||||
|
||||
return render_template('recharge_activity_form.html', activity=activity)
|
||||
|
||||
@app.route('/recharge_activities/delete/<int:id>', endpoint='recharge_activity_delete')
|
||||
@login_required
|
||||
@permission_required('recharge_activity_edit')
|
||||
def recharge_activity_delete(id):
|
||||
activity = db.session.get(RechargeActivity, id)
|
||||
if activity:
|
||||
activity.status = 0
|
||||
db.session.commit()
|
||||
log_operation('停用充值活动', f'活动ID:{id}')
|
||||
flash('充值活动已停用', 'success')
|
||||
return redirect(url_for('recharge_activity_list'))
|
||||
|
||||
@app.route('/recharges', endpoint='recharge_list')
|
||||
@login_required
|
||||
@permission_required('recharge_view')
|
||||
def recharge_list():
|
||||
keyword = request.args.get('keyword', '')
|
||||
query = RechargeRecord.query
|
||||
if keyword:
|
||||
students = filter_students_query(
|
||||
Student.query.filter(Student.name.contains(keyword)),
|
||||
).all()
|
||||
student_ids = [s.id for s in students]
|
||||
query = query.filter(RechargeRecord.student_id.in_(student_ids)) if student_ids else query.filter(False)
|
||||
if is_teacher_role():
|
||||
allowed = {s.id for s in filter_students_query(Student.query).all()}
|
||||
records = [
|
||||
r for r in query.order_by(RechargeRecord.created_at.desc()).all()
|
||||
if r.student_id in allowed
|
||||
]
|
||||
else:
|
||||
records = query.order_by(RechargeRecord.created_at.desc()).all()
|
||||
return render_template('recharge_list.html', records=records, keyword=keyword)
|
||||
|
||||
@app.route('/recharges/add', methods=['GET', 'POST'], endpoint='recharge_add')
|
||||
@login_required
|
||||
@permission_required('recharge_add')
|
||||
def recharge_add():
|
||||
if request.method == 'POST':
|
||||
student_id = int(request.form.get('student_id'))
|
||||
course_id = int(request.form.get('course_id'))
|
||||
amount = float(request.form.get('amount', 0))
|
||||
if amount <= 0:
|
||||
flash('充值金额必须大于 0', 'danger')
|
||||
return redirect(url_for('recharge_add'))
|
||||
payment_method = request.form.get('payment_method', '微信')
|
||||
remark = request.form.get('remark', '')
|
||||
|
||||
course = db.session.get(Course, course_id)
|
||||
normal_hours = rules.calc_normal_hours_from_amount(
|
||||
amount, course.price_per_hour if course else 0
|
||||
)
|
||||
gifted_hours, activity_id = rules.match_recharge_activities(
|
||||
student_id, course_id, amount
|
||||
)
|
||||
|
||||
record = RechargeRecord(
|
||||
student_id=student_id, course_id=course_id, amount=amount,
|
||||
activity_id=activity_id,
|
||||
normal_hours=normal_hours, gifted_hours=gifted_hours,
|
||||
payment_method=payment_method, operator_id=session.get('user_id'),
|
||||
remark=remark,
|
||||
)
|
||||
db.session.add(record)
|
||||
|
||||
account = rules.lock_student_account(student_id, course_id)
|
||||
is_new = not account
|
||||
if is_new:
|
||||
account = StudentAccount(student_id=student_id, course_id=course_id)
|
||||
db.session.add(account)
|
||||
db.session.flush()
|
||||
|
||||
before = rules.account_snapshot_dict(account)
|
||||
rules.apply_recharge_account(
|
||||
account, amount, normal_hours, gifted_hours, is_new_account=is_new,
|
||||
)
|
||||
db.session.commit()
|
||||
log_operation(
|
||||
'充值',
|
||||
f'学员ID:{student_id} 充值{amount}元(正课{normal_hours}+赠课{int(gifted_hours)})',
|
||||
before=before, after=rules.account_snapshot_dict(account),
|
||||
)
|
||||
flash(f'充值成功!正课{normal_hours}课时,赠课{int(gifted_hours)}课时', 'success')
|
||||
return redirect(url_for('recharge_list'))
|
||||
|
||||
students = filter_students_query(Student.query.filter_by(status=1)).all()
|
||||
courses = Course.query.filter_by(status=1).all()
|
||||
return render_template('recharge_form.html', students=students, courses=courses)
|
||||
|
||||
@app.route('/referral_reward', methods=['GET', 'POST'], endpoint='referral_reward')
|
||||
@login_required
|
||||
@permission_required('recharge_add')
|
||||
def referral_reward():
|
||||
"""§一 1.5 老带新奖励(管理员手动发放)"""
|
||||
if request.method == 'POST':
|
||||
referrer_id = int(request.form.get('referrer_id'))
|
||||
student_id = int(request.form.get('student_id'))
|
||||
course_id = int(request.form.get('course_id'))
|
||||
reward_hours = rules.normalize_consumption_hours(
|
||||
request.form.get('reward_hours', 1)
|
||||
)
|
||||
remark = request.form.get('remark', '老带新奖励')
|
||||
|
||||
account = StudentAccount.query.filter_by(
|
||||
student_id=referrer_id, course_id=course_id
|
||||
).first()
|
||||
if not account:
|
||||
account = StudentAccount(student_id=referrer_id, course_id=course_id)
|
||||
db.session.add(account)
|
||||
db.session.flush()
|
||||
account.gifted_hours = float(account.gifted_hours or 0) + reward_hours
|
||||
db.session.commit()
|
||||
log_operation(
|
||||
'老带新奖励',
|
||||
f'推荐人学员ID:{referrer_id} 获赠{reward_hours}课时 新学员ID:{student_id}',
|
||||
)
|
||||
flash(f'已向推荐人发放 {reward_hours} 课时赠课', 'success')
|
||||
return redirect(url_for('referral_reward'))
|
||||
|
||||
students = Student.query.filter_by(status=1).all()
|
||||
courses = Course.query.filter_by(status=1).all()
|
||||
return render_template(
|
||||
'referral_reward.html', students=students, courses=courses
|
||||
)
|
||||
|
||||
@app.route('/api/student_account/<int:student_id>/<int:course_id>')
|
||||
@login_required
|
||||
@permission_required('recharge_view')
|
||||
def api_student_account(student_id, course_id):
|
||||
account = StudentAccount.query.filter_by(
|
||||
student_id=student_id, course_id=course_id
|
||||
).first()
|
||||
if account:
|
||||
nh = int(float(account.normal_hours or 0))
|
||||
gh = int(float(account.gifted_hours or 0))
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'normal_hours': nh,
|
||||
'gifted_hours': gh,
|
||||
'total_hours': nh + gh,
|
||||
'consumed_normal': float(account.consumed_normal or 0),
|
||||
'consumed_gifted': float(account.consumed_gifted or 0),
|
||||
'cumulative_amount': float(account.cumulative_amount or 0),
|
||||
'is_stopped': bool(account.is_stopped),
|
||||
},
|
||||
})
|
||||
return jsonify({'success': False, 'message': '未找到账户'})
|
||||
|
||||
@app.route('/api/student_accounts/<int:student_id>')
|
||||
@login_required
|
||||
@permission_required('recharge_view')
|
||||
def api_student_accounts(student_id):
|
||||
accounts = StudentAccount.query.filter_by(student_id=student_id).all()
|
||||
result = []
|
||||
for a in accounts:
|
||||
course = db.session.get(Course, a.course_id)
|
||||
nh = int(float(a.normal_hours or 0))
|
||||
gh = int(float(a.gifted_hours or 0))
|
||||
result.append({
|
||||
'course_id': a.course_id,
|
||||
'course_name': course.name if course else '',
|
||||
'normal_hours': nh,
|
||||
'gifted_hours': gh,
|
||||
'cumulative_amount': float(a.cumulative_amount or 0),
|
||||
'is_stopped': bool(a.is_stopped),
|
||||
})
|
||||
return jsonify({'success': True, 'data': result})
|
||||
|
||||
@app.route('/api/calculate_recharge', methods=['POST'])
|
||||
@login_required
|
||||
@permission_required('recharge_view')
|
||||
def api_calculate_recharge():
|
||||
data = request.get_json()
|
||||
student_id = data.get('student_id')
|
||||
course_id = data.get('course_id')
|
||||
amount = float(data.get('amount', 0))
|
||||
|
||||
course = db.session.get(Course, course_id)
|
||||
normal_hours = rules.calc_normal_hours_from_amount(
|
||||
amount, course.price_per_hour if course else 0
|
||||
)
|
||||
gifted_hours, _ = rules.match_recharge_activities(student_id, course_id, amount)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'normal_hours': normal_hours,
|
||||
'gifted_hours': int(gifted_hours),
|
||||
'total_hours': normal_hours + int(gifted_hours),
|
||||
},
|
||||
})
|
||||
|
||||
@app.route('/recharge_activities/export', endpoint='recharge_activity_export')
|
||||
@login_required
|
||||
@permission_required('recharge_activity_export')
|
||||
def recharge_activity_export():
|
||||
activities = RechargeActivity.query.order_by(RechargeActivity.id.desc()).all()
|
||||
headers = ['序号', '活动名称', '触发金额', '赠送课时', '可叠加', '期限活动',
|
||||
'开始日期', '结束日期', '状态', '备注']
|
||||
rows = []
|
||||
for i, a in enumerate(activities, 1):
|
||||
rows.append([
|
||||
i, a.name, a.trigger_amount, a.gifted_hours,
|
||||
'是' if a.can_stack else '否',
|
||||
'是' if a.is_time_limited else '否',
|
||||
str(a.start_date) if a.start_date else '',
|
||||
str(a.end_date) if a.end_date else '',
|
||||
'启用' if a.status == 1 else '禁用',
|
||||
'',
|
||||
])
|
||||
return export_excel('充值优惠活动', headers, rows)
|
||||
|
||||
@app.route('/recharges/export', endpoint='recharge_export')
|
||||
@login_required
|
||||
@permission_required('recharge_export')
|
||||
def recharge_export():
|
||||
from models import User
|
||||
records = RechargeRecord.query.order_by(RechargeRecord.id.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)
|
||||
operator = db.session.get(User, r.operator_id) if r.operator_id else None
|
||||
rows.append([
|
||||
i, student.name if student else '', course.name if course else '',
|
||||
r.amount or 0, r.normal_hours or 0, r.gifted_hours or 0,
|
||||
r.payment_method or '', operator.real_name if operator else '',
|
||||
r.remark or '',
|
||||
r.created_at.strftime('%Y-%m-%d %H:%M') if r.created_at else '',
|
||||
])
|
||||
return export_excel('充值记录', headers, rows)
|
||||
|
||||
|
||||
def _parse_date(date_str):
|
||||
if date_str:
|
||||
try:
|
||||
return datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||
except (ValueError, TypeError):
|
||||
return None
|
||||
return None
|
||||
Reference in New Issue
Block a user