597 lines
27 KiB
Python
597 lines
27 KiB
Python
|
|
"""学生课程管理系统 - SQLAlchemy 数据模型"""
|
|||
|
|
from datetime import datetime, date
|
|||
|
|
from flask_sqlalchemy import SQLAlchemy
|
|||
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|||
|
|
|
|||
|
|
db = SQLAlchemy()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Model(db.Model):
|
|||
|
|
"""ORM 基类:表名/列名加反引号,兼容 Doris 保留字(roles、classes、date 等)"""
|
|||
|
|
__abstract__ = True
|
|||
|
|
__table_args__ = {'quote': True}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Role(Model):
|
|||
|
|
"""角色表"""
|
|||
|
|
__tablename__ = 'roles'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
name = db.Column(db.String(50), unique=True, nullable=False)
|
|||
|
|
description = db.Column(db.String(200))
|
|||
|
|
permissions = db.Column(db.Text)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'name': self.name, 'description': self.description,
|
|||
|
|
'permissions': self.permissions, 'remark': self.remark,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class User(Model):
|
|||
|
|
"""用户表"""
|
|||
|
|
__tablename__ = 'users'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
username = db.Column(db.String(50), unique=True, nullable=False)
|
|||
|
|
password = db.Column(db.String(200), nullable=False)
|
|||
|
|
real_name = db.Column(db.String(50), nullable=False)
|
|||
|
|
role_id = db.Column(db.Integer, db.ForeignKey('roles.id'), nullable=False)
|
|||
|
|
phone = db.Column(db.String(20))
|
|||
|
|
email = db.Column(db.String(100))
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
remark = db.Column(db.String(200))
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
|||
|
|
|
|||
|
|
role = db.relationship('Role', backref='users')
|
|||
|
|
|
|||
|
|
def set_password(self, pwd):
|
|||
|
|
self.password = generate_password_hash(pwd)
|
|||
|
|
|
|||
|
|
def check_password(self, pwd):
|
|||
|
|
return check_password_hash(self.password, pwd)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def is_authenticated(self):
|
|||
|
|
return True
|
|||
|
|
|
|||
|
|
def has_permission(self, perm):
|
|||
|
|
"""检查用户是否拥有指定权限,超级管理员角色拥有所有权限"""
|
|||
|
|
if not self.role:
|
|||
|
|
return False
|
|||
|
|
if self.role.name == '超级管理员':
|
|||
|
|
return True
|
|||
|
|
perms_str = self.role.permissions or ''
|
|||
|
|
if perms_str == 'all':
|
|||
|
|
return True
|
|||
|
|
return perm in perms_str.split(',')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'username': self.username, 'real_name': self.real_name,
|
|||
|
|
'role_id': self.role_id, 'phone': self.phone, 'email': self.email,
|
|||
|
|
'status': self.status, 'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Teacher(Model):
|
|||
|
|
"""老师表"""
|
|||
|
|
__tablename__ = 'teachers'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
|
|||
|
|
name = db.Column(db.String(50), nullable=False)
|
|||
|
|
phone = db.Column(db.String(20))
|
|||
|
|
specialty = db.Column(db.String(200))
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
user = db.relationship('User', backref='teacher_profile')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'user_id': self.user_id, 'name': self.name,
|
|||
|
|
'phone': self.phone, 'specialty': self.specialty,
|
|||
|
|
'status': self.status, 'remark': self.remark,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Student(Model):
|
|||
|
|
"""学员表"""
|
|||
|
|
__tablename__ = 'students'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
name = db.Column(db.String(50), nullable=False)
|
|||
|
|
display_name = db.Column(db.String(50))
|
|||
|
|
nickname = db.Column(db.String(50))
|
|||
|
|
gender = db.Column(db.String(10))
|
|||
|
|
phone = db.Column(db.String(20))
|
|||
|
|
parent_phone = db.Column(db.String(20))
|
|||
|
|
birthday = db.Column(db.Date)
|
|||
|
|
address = db.Column(db.String(200))
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
source = db.Column(db.String(50), default='新学员')
|
|||
|
|
referrer_id = db.Column(db.Integer)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'name': self.name,
|
|||
|
|
'display_name': self.display_name, 'nickname': self.nickname,
|
|||
|
|
'gender': self.gender,
|
|||
|
|
'phone': self.phone, 'parent_phone': self.parent_phone,
|
|||
|
|
'birthday': str(self.birthday) if self.birthday else None,
|
|||
|
|
'address': self.address, 'remark': self.remark,
|
|||
|
|
'status': self.status, 'source': self.source,
|
|||
|
|
'referrer_id': self.referrer_id,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Course(Model):
|
|||
|
|
"""课程表"""
|
|||
|
|
__tablename__ = 'courses'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
name = db.Column(db.String(100), nullable=False)
|
|||
|
|
course_code = db.Column(db.String(20))
|
|||
|
|
type = db.Column(db.String(50), default='一对一')
|
|||
|
|
level = db.Column(db.String(50))
|
|||
|
|
description = db.Column(db.Text)
|
|||
|
|
price_per_hour = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
total_hours = db.Column(db.Integer, default=0)
|
|||
|
|
material_fee = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'name': self.name, 'type': self.type,
|
|||
|
|
'level': self.level, 'description': self.description,
|
|||
|
|
'price_per_hour': float(self.price_per_hour) if self.price_per_hour else 0,
|
|||
|
|
'total_hours': self.total_hours,
|
|||
|
|
'material_fee': float(self.material_fee or 0),
|
|||
|
|
'remark': self.remark,
|
|||
|
|
'status': self.status, 'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Class_(Model):
|
|||
|
|
"""班级表"""
|
|||
|
|
__tablename__ = 'classes'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
name = db.Column(db.String(100), nullable=False)
|
|||
|
|
course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
teacher_id = db.Column(db.Integer, db.ForeignKey('teachers.id'), nullable=False)
|
|||
|
|
start_date = db.Column(db.Date)
|
|||
|
|
end_date = db.Column(db.Date)
|
|||
|
|
schedule = db.Column(db.String(200))
|
|||
|
|
max_students = db.Column(db.Integer, default=20)
|
|||
|
|
current_students = db.Column(db.Integer, default=0)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
course = db.relationship('Course', backref='classes')
|
|||
|
|
teacher = db.relationship('Teacher', backref='classes')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'name': self.name, 'course_id': self.course_id,
|
|||
|
|
'teacher_id': self.teacher_id,
|
|||
|
|
'start_date': str(self.start_date) if self.start_date else None,
|
|||
|
|
'end_date': str(self.end_date) if self.end_date else None,
|
|||
|
|
'schedule': self.schedule, 'max_students': self.max_students,
|
|||
|
|
'current_students': self.current_students,
|
|||
|
|
'remark': self.remark,
|
|||
|
|
'status': self.status, 'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ClassStudent(Model):
|
|||
|
|
"""班级学员关联表"""
|
|||
|
|
__tablename__ = 'class_students'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
class_id = db.Column(db.Integer, db.ForeignKey('classes.id'), nullable=False)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
join_date = db.Column(db.Date, nullable=False)
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
class_ = db.relationship('Class_', backref='student_relations')
|
|||
|
|
student = db.relationship('Student', backref='class_relations')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'class_id': self.class_id, 'student_id': self.student_id,
|
|||
|
|
'join_date': str(self.join_date), 'status': self.status}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class StudentAccount(Model):
|
|||
|
|
"""学员课时账户表"""
|
|||
|
|
__tablename__ = 'student_accounts'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
normal_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
gifted_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
consumed_normal = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
consumed_gifted = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
normal_validity = db.Column(db.String(20), default='permanent')
|
|||
|
|
normal_expiry_date = db.Column(db.Date)
|
|||
|
|
gifted_validity = db.Column(db.String(20), default='permanent')
|
|||
|
|
gifted_expiry_date = db.Column(db.Date)
|
|||
|
|
is_stopped = db.Column(db.SmallInteger, default=0)
|
|||
|
|
stop_end_date = db.Column(db.Date)
|
|||
|
|
cumulative_amount = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
cumulative_start_date = db.Column(db.Date)
|
|||
|
|
unit_price = db.Column(db.Numeric(10, 4), default=0)
|
|||
|
|
original_price_per_lesson = db.Column(db.Numeric(10, 4))
|
|||
|
|
account_status = db.Column(db.String(20), default='active')
|
|||
|
|
version = db.Column(db.Integer, default=0)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
|||
|
|
|
|||
|
|
student = db.relationship('Student', backref='accounts')
|
|||
|
|
course = db.relationship('Course', backref='accounts')
|
|||
|
|
monthly_snapshots = db.relationship('MonthlySnapshot', backref='account', lazy='dynamic')
|
|||
|
|
class_records = db.relationship('ClassRecord', backref='account', lazy='dynamic')
|
|||
|
|
|
|||
|
|
__table_args__ = (
|
|||
|
|
db.UniqueConstraint('student_id', 'course_id'),
|
|||
|
|
{'quote': True},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def total_hours(self):
|
|||
|
|
return float(self.normal_hours or 0) + float(self.gifted_hours or 0)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def total_consumed(self):
|
|||
|
|
return float(self.consumed_normal or 0) + float(self.consumed_gifted or 0)
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'student_id': self.student_id, 'course_id': self.course_id,
|
|||
|
|
'normal_hours': float(self.normal_hours or 0),
|
|||
|
|
'gifted_hours': float(self.gifted_hours or 0),
|
|||
|
|
'consumed_normal': float(self.consumed_normal or 0),
|
|||
|
|
'consumed_gifted': float(self.consumed_gifted or 0),
|
|||
|
|
'total_hours': self.total_hours,
|
|||
|
|
'is_stopped': self.is_stopped,
|
|||
|
|
'cumulative_amount': float(self.cumulative_amount or 0)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RechargeActivity(Model):
|
|||
|
|
"""充值优惠活动表"""
|
|||
|
|
__tablename__ = 'recharge_activities'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
name = db.Column(db.String(100), nullable=False)
|
|||
|
|
trigger_amount = db.Column(db.Numeric(10, 2), nullable=False)
|
|||
|
|
gifted_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
gift_type = db.Column(db.String(50), default='课时')
|
|||
|
|
is_cumulative = db.Column(db.SmallInteger, default=0)
|
|||
|
|
cumulative_window = db.Column(db.Integer, default=0)
|
|||
|
|
cumulative_clear = db.Column(db.SmallInteger, default=0)
|
|||
|
|
allow_multi_trigger = db.Column(db.SmallInteger, default=0)
|
|||
|
|
is_time_limited = db.Column(db.SmallInteger, default=0)
|
|||
|
|
start_date = db.Column(db.Date)
|
|||
|
|
end_date = db.Column(db.Date)
|
|||
|
|
target_audience = db.Column(db.String(50), default='all')
|
|||
|
|
can_stack = db.Column(db.SmallInteger, default=1)
|
|||
|
|
gift_cap = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
group_discount = db.Column(db.SmallInteger, default=0)
|
|||
|
|
group_min_people = db.Column(db.Integer, default=0)
|
|||
|
|
group_extra_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
referral_reward = db.Column(db.SmallInteger, default=0)
|
|||
|
|
referral_reward_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
reward_timing = db.Column(db.String(50), default='immediate')
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'name': self.name,
|
|||
|
|
'trigger_amount': float(self.trigger_amount or 0),
|
|||
|
|
'gifted_hours': float(self.gifted_hours or 0),
|
|||
|
|
'gift_type': self.gift_type,
|
|||
|
|
'is_cumulative': self.is_cumulative,
|
|||
|
|
'cumulative_window': self.cumulative_window,
|
|||
|
|
'allow_multi_trigger': self.allow_multi_trigger,
|
|||
|
|
'is_time_limited': self.is_time_limited,
|
|||
|
|
'start_date': str(self.start_date) if self.start_date else None,
|
|||
|
|
'end_date': str(self.end_date) if self.end_date else None,
|
|||
|
|
'target_audience': self.target_audience,
|
|||
|
|
'can_stack': self.can_stack,
|
|||
|
|
'gift_cap': float(self.gift_cap or 0),
|
|||
|
|
'group_discount': self.group_discount,
|
|||
|
|
'referral_reward': self.referral_reward,
|
|||
|
|
'reward_timing': self.reward_timing,
|
|||
|
|
'status': self.status}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RechargeRecord(Model):
|
|||
|
|
"""充值记录表"""
|
|||
|
|
__tablename__ = 'recharge_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
activity_id = db.Column(db.Integer, db.ForeignKey('recharge_activities.id'))
|
|||
|
|
amount = db.Column(db.Numeric(10, 2), nullable=False)
|
|||
|
|
normal_hours = db.Column(db.Numeric(10, 2), nullable=False)
|
|||
|
|
gifted_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
payment_method = db.Column(db.String(50))
|
|||
|
|
operator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
gift_clawed_back = db.Column(db.SmallInteger, default=0)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
student = db.relationship('Student', backref='recharge_records')
|
|||
|
|
course = db.relationship('Course', backref='recharge_records')
|
|||
|
|
activity = db.relationship('RechargeActivity', backref='records')
|
|||
|
|
operator = db.relationship('User', backref='recharge_operations')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'student_id': self.student_id, 'course_id': self.course_id,
|
|||
|
|
'activity_id': self.activity_id,
|
|||
|
|
'amount': float(self.amount or 0),
|
|||
|
|
'normal_hours': float(self.normal_hours or 0),
|
|||
|
|
'gifted_hours': float(self.gifted_hours or 0),
|
|||
|
|
'payment_method': self.payment_method,
|
|||
|
|
'operator_id': self.operator_id,
|
|||
|
|
'remark': self.remark,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ConsumptionRecord(Model):
|
|||
|
|
"""消课记录表"""
|
|||
|
|
__tablename__ = 'consumption_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
class_id = db.Column(db.Integer, db.ForeignKey('classes.id'))
|
|||
|
|
course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
hours_consumed = db.Column(db.Numeric(10, 2), nullable=False)
|
|||
|
|
consume_type = db.Column(db.String(20), nullable=False)
|
|||
|
|
normal_consumed = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
gifted_consumed = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
operator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|||
|
|
consume_date = db.Column(db.Date, nullable=False)
|
|||
|
|
is_makeup = db.Column(db.SmallInteger, default=0)
|
|||
|
|
is_trial = db.Column(db.SmallInteger, default=0)
|
|||
|
|
unit_price_at_consume = db.Column(db.Numeric(10, 4))
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
student = db.relationship('Student', backref='consumption_records')
|
|||
|
|
class_ = db.relationship('Class_', backref='consumption_records')
|
|||
|
|
course = db.relationship('Course', backref='consumption_records')
|
|||
|
|
operator = db.relationship('User', backref='consumption_operations')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'student_id': self.student_id, 'class_id': self.class_id,
|
|||
|
|
'course_id': self.course_id,
|
|||
|
|
'hours_consumed': float(self.hours_consumed or 0),
|
|||
|
|
'consume_type': self.consume_type,
|
|||
|
|
'normal_consumed': float(self.normal_consumed or 0),
|
|||
|
|
'gifted_consumed': float(self.gifted_consumed or 0),
|
|||
|
|
'consume_date': str(self.consume_date),
|
|||
|
|
'is_makeup': self.is_makeup,
|
|||
|
|
'is_trial': self.is_trial,
|
|||
|
|
'remark': self.remark,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class RefundRecord(Model):
|
|||
|
|
"""退费记录表"""
|
|||
|
|
__tablename__ = 'refund_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
refund_amount = db.Column(db.Numeric(10, 2), nullable=False)
|
|||
|
|
remaining_normal_hours = db.Column(db.Numeric(10, 2))
|
|||
|
|
remaining_gifted_hours = db.Column(db.Numeric(10, 2))
|
|||
|
|
deduct_gifted_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
deduct_gifted_amount = db.Column(db.Numeric(12, 2), default=0)
|
|||
|
|
promo_clawback_hours = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
consumed_hours_value = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
material_fee_per_quarter = db.Column(db.Numeric(10, 2), default=250)
|
|||
|
|
quarters = db.Column(db.Integer, default=0)
|
|||
|
|
total_deduct = db.Column(db.Numeric(10, 2), default=0)
|
|||
|
|
reason = db.Column(db.Text)
|
|||
|
|
operator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
student = db.relationship('Student', backref='refund_records')
|
|||
|
|
course = db.relationship('Course', backref='refund_records')
|
|||
|
|
operator = db.relationship('User', backref='refund_operations')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'student_id': self.student_id, 'course_id': self.course_id,
|
|||
|
|
'refund_amount': float(self.refund_amount or 0),
|
|||
|
|
'remaining_normal_hours': float(self.remaining_normal_hours or 0),
|
|||
|
|
'remaining_gifted_hours': float(self.remaining_gifted_hours or 0),
|
|||
|
|
'deduct_gifted_hours': float(self.deduct_gifted_hours or 0),
|
|||
|
|
'consumed_hours_value': float(self.consumed_hours_value or 0),
|
|||
|
|
'material_fee_per_quarter': float(self.material_fee_per_quarter or 0),
|
|||
|
|
'quarters': self.quarters,
|
|||
|
|
'total_deduct': float(self.total_deduct or 0),
|
|||
|
|
'reason': self.reason,
|
|||
|
|
'status': self.status,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class TransferRecord(Model):
|
|||
|
|
"""转课记录表"""
|
|||
|
|
__tablename__ = 'transfer_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
from_student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
to_student_id = db.Column(db.Integer, db.ForeignKey('students.id'))
|
|||
|
|
from_course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
to_course_id = db.Column(db.Integer, db.ForeignKey('courses.id'))
|
|||
|
|
transfer_type = db.Column(db.String(20), nullable=False)
|
|||
|
|
transfer_hours = db.Column(db.Numeric(10, 2), nullable=False)
|
|||
|
|
transfer_amount = db.Column(db.Numeric(10, 2))
|
|||
|
|
operator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
from_student = db.relationship('Student', foreign_keys=[from_student_id], backref='transfers_out')
|
|||
|
|
to_student = db.relationship('Student', foreign_keys=[to_student_id], backref='transfers_in')
|
|||
|
|
from_course = db.relationship('Course', foreign_keys=[from_course_id])
|
|||
|
|
to_course = db.relationship('Course', foreign_keys=[to_course_id])
|
|||
|
|
operator = db.relationship('User', backref='transfer_operations')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'from_student_id': self.from_student_id,
|
|||
|
|
'to_student_id': self.to_student_id,
|
|||
|
|
'from_course_id': self.from_course_id, 'to_course_id': self.to_course_id,
|
|||
|
|
'transfer_type': self.transfer_type,
|
|||
|
|
'transfer_hours': float(self.transfer_hours or 0),
|
|||
|
|
'transfer_amount': float(self.transfer_amount or 0),
|
|||
|
|
'remark': self.remark,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class StopRecord(Model):
|
|||
|
|
"""停课记录表"""
|
|||
|
|
__tablename__ = 'stop_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
course_id = db.Column(db.Integer, db.ForeignKey('courses.id'), nullable=False)
|
|||
|
|
start_date = db.Column(db.Date, nullable=False)
|
|||
|
|
end_date = db.Column(db.Date, nullable=False)
|
|||
|
|
reason = db.Column(db.Text)
|
|||
|
|
operator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|||
|
|
status = db.Column(db.SmallInteger, default=1)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
student = db.relationship('Student', backref='stop_records')
|
|||
|
|
course = db.relationship('Course', backref='stop_records')
|
|||
|
|
operator = db.relationship('User', backref='stop_operations')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'student_id': self.student_id, 'course_id': self.course_id,
|
|||
|
|
'start_date': str(self.start_date), 'end_date': str(self.end_date),
|
|||
|
|
'reason': self.reason, 'status': self.status,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AttendanceRecord(Model):
|
|||
|
|
"""考勤记录表"""
|
|||
|
|
__tablename__ = 'attendance_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
student_id = db.Column(db.Integer, db.ForeignKey('students.id'), nullable=False)
|
|||
|
|
class_id = db.Column(db.Integer, db.ForeignKey('classes.id'), nullable=False)
|
|||
|
|
date = db.Column(db.Date, nullable=False)
|
|||
|
|
status = db.Column(db.String(20), nullable=False)
|
|||
|
|
remark = db.Column(db.Text)
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
student = db.relationship('Student', backref='attendance_records')
|
|||
|
|
class_ = db.relationship('Class_', backref='attendance_records')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'student_id': self.student_id, 'class_id': self.class_id,
|
|||
|
|
'date': str(self.date), 'status': self.status, 'remark': self.remark}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class OperationLog(Model):
|
|||
|
|
"""操作日志表"""
|
|||
|
|
__tablename__ = 'operation_logs'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
|||
|
|
operation_type = db.Column(db.String(50), nullable=False)
|
|||
|
|
operation_detail = db.Column(db.Text)
|
|||
|
|
ip_address = db.Column(db.String(50))
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
user = db.relationship('User', backref='operation_logs')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'user_id': self.user_id,
|
|||
|
|
'operation_type': self.operation_type,
|
|||
|
|
'operation_detail': self.operation_detail,
|
|||
|
|
'ip_address': self.ip_address,
|
|||
|
|
'created_at': str(self.created_at)}
|
|||
|
|
|
|||
|
|
|
|||
|
|
class Schedule(Model):
|
|||
|
|
"""课程安排表"""
|
|||
|
|
__tablename__ = 'schedules'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
class_id = db.Column(db.Integer, db.ForeignKey('classes.id'), nullable=False)
|
|||
|
|
date = db.Column(db.Date, nullable=False)
|
|||
|
|
start_time = db.Column(db.String(10), nullable=False)
|
|||
|
|
end_time = db.Column(db.String(10), nullable=False)
|
|||
|
|
teacher_id = db.Column(db.Integer, db.ForeignKey('teachers.id'), nullable=False)
|
|||
|
|
topic = db.Column(db.String(200))
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
class_ = db.relationship('Class_', backref='schedules')
|
|||
|
|
teacher = db.relationship('Teacher', backref='schedules')
|
|||
|
|
|
|||
|
|
def to_dict(self):
|
|||
|
|
return {'id': self.id, 'class_id': self.class_id,
|
|||
|
|
'date': str(self.date), 'start_time': self.start_time,
|
|||
|
|
'end_time': self.end_time, 'teacher_id': self.teacher_id,
|
|||
|
|
'topic': self.topic}
|
|||
|
|
|
|||
|
|
|
|||
|
|
# ========== 课时核对表(Excel 流水账)==========
|
|||
|
|
|
|||
|
|
COURSE_CODES = ('scratch', 'python', 'c++', 'wedo')
|
|||
|
|
|
|||
|
|
|
|||
|
|
class MonthlySnapshot(Model):
|
|||
|
|
"""月度课时快照:每个学员课程账户每月一条"""
|
|||
|
|
__tablename__ = 'monthly_snapshots'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
account_id = db.Column(db.Integer, db.ForeignKey('student_accounts.id'), nullable=False)
|
|||
|
|
year = db.Column(db.SmallInteger, nullable=False)
|
|||
|
|
month = db.Column(db.SmallInteger, nullable=False)
|
|||
|
|
seq_no = db.Column(db.Integer)
|
|||
|
|
prev_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
prev_gift_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
prev_total_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
prev_balance = db.Column(db.Numeric(12, 2))
|
|||
|
|
new_signup_amount = db.Column(db.Numeric(12, 2))
|
|||
|
|
new_signup_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
new_signup_gift_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
renewal_amount = db.Column(db.Numeric(12, 2))
|
|||
|
|
renewal_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
renewal_gift_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
consumed_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
consumed_amount = db.Column(db.Numeric(12, 2))
|
|||
|
|
refund_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
account_fee = db.Column(db.Numeric(12, 2))
|
|||
|
|
refund_gift_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
refund_amount = db.Column(db.Numeric(12, 2))
|
|||
|
|
end_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
end_gift_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
end_total_lessons = db.Column(db.Numeric(10, 2))
|
|||
|
|
end_balance = db.Column(db.Numeric(12, 2))
|
|||
|
|
unit_price = db.Column(db.Numeric(10, 4))
|
|||
|
|
notes = db.Column(db.Text)
|
|||
|
|
source_file = db.Column(db.String(200))
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
__table_args__ = (
|
|||
|
|
db.UniqueConstraint('account_id', 'year', 'month', name='uq_snapshot_account_month'),
|
|||
|
|
{'quote': True},
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
@property
|
|||
|
|
def period_label(self):
|
|||
|
|
return f'{self.year}-{self.month:02d}'
|
|||
|
|
|
|||
|
|
|
|||
|
|
class ClassRecord(Model):
|
|||
|
|
"""上课记录:从 Excel「上课情况」列解析"""
|
|||
|
|
__tablename__ = 'class_records'
|
|||
|
|
id = db.Column(db.Integer, primary_key=True)
|
|||
|
|
account_id = db.Column(db.Integer, db.ForeignKey('student_accounts.id'), nullable=False)
|
|||
|
|
year = db.Column(db.SmallInteger, nullable=False)
|
|||
|
|
month = db.Column(db.SmallInteger, nullable=False)
|
|||
|
|
class_date = db.Column(db.Date, nullable=False)
|
|||
|
|
lessons_consumed = db.Column(db.Numeric(10, 2), default=2)
|
|||
|
|
raw_text = db.Column(db.String(50))
|
|||
|
|
created_at = db.Column(db.DateTime, default=datetime.now)
|
|||
|
|
|
|||
|
|
__table_args__ = (
|
|||
|
|
db.UniqueConstraint(
|
|||
|
|
'account_id', 'year', 'month', 'class_date', 'lessons_consumed',
|
|||
|
|
name='uq_class_record_day',
|
|||
|
|
),
|
|||
|
|
{'quote': True},
|
|||
|
|
)
|