This commit is contained in:
liuhaoran
2026-06-20 19:32:29 +08:00
parent accec93854
commit 45fbe4fbba
23 changed files with 540 additions and 93 deletions
+90 -23
View File
@@ -14,6 +14,7 @@ class CourseHomework(models.Model):
requirement = fields.Html(string='作业要求')
course_id = fields.Many2one('learning.course', string='所属课程', required=True, ondelete='cascade')
chapter_id = fields.Many2one('course.chapter', string='关联章节')
total_score = fields.Float(string='作业总分', default=100.0, required=True)
publish_time = fields.Datetime(string='发布时间')
deadline = fields.Datetime(string='提交截止时间', required=True)
@@ -28,12 +29,13 @@ class CourseHomework(models.Model):
total_students = fields.Integer(string='应提交人数', compute='_compute_submit_stats')
submitted_count = fields.Integer(string='已提交人数', compute='_compute_submit_stats')
late_count = fields.Integer(string='逾期提交人数', compute='_compute_submit_stats')
course_score_item_id=fields.Many2one('course.score.item',string='作业类型')
unsubmitted_count = fields.Integer(string='未提交人数', compute='_compute_submit_stats')
# 添加教学班关联
teaching_class_id = fields.Many2one('course.teaching_class', string='教学班',
ondelete='cascade', domain="[('course_id', '=', course_id)]")
submit_ids = fields.One2many('course.homework.submit', 'homework_id', string='提交记录')
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项')
score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项目')
# ====================== 新增:自动计算提交通道是否开启 ======================
@api.depends('state', 'deadline')
@@ -80,37 +82,61 @@ class CourseHomework(models.Model):
self.publish_time = fields.Datetime.now()
# 1. 批量为本班学生生成空白未提交记录
self._auto_create_empty_submit()
# 新增:自动生成对应作业的学生成绩明细
self._auto_create_homework_score_detail()
# 2. 推送作业通知给学生
self._send_homework_notice_to_students()
def _send_homework_notice_to_students(self):
"""发布作业后推送消息给教学班学生"""
def _auto_create_homework_score_detail(self):
self.ensure_one()
if not self.teaching_class_id:
# 修正字段名 course_score_item_id
if not self.teaching_class_id or not self.course_score_item_id:
return
# 获取当前教学班所有已选课学生
enrolls = self.env['course.teaching_class.enrollment'].search([
score_item = self.course_score_item_id
# 获取教学班在册学生
enroll_records = self.env['course.teaching_class.enrollment'].search([
('teaching_class_id', '=', self.teaching_class_id.id),
('state', '=', 'enrolled')
])
student_users = enrolls.mapped('student_id.user_id').filtered(lambda u: u)
if not student_users:
student_ids = enroll_records.mapped('student_id').ids
if not student_ids:
return
# 消息内容
subject = f"新作业发布:{self.name}"
body = f"""
<p>课程:{self.course_id.name}</p>
<p>作业名称:{self.name}</p>
<p>截止时间:{self.deadline or '无'}</p>
<p>作业要求:{self.requirement or '无'}</p>
"""
# 发送消息(关联当前作业记录,学生在消息中心可直接跳转)
self.message_post(
subject=subject,
body=body,
partner_ids=student_users.mapped('partner_id').ids,
subtype_xmlid='mail.mt_comment'
)
# 查询当前作业+当前分项已存在明细
exist_details = self.env['course.student.score.detail'].search([
('course_id', '=', self.course_id.id),
('score_item_id', '=', score_item.id),
('homework_id', '=', self.id),
('student_id', 'in', student_ids)
])
exist_student_ids = exist_details.mapped('student_id').ids
target_student_ids = [sid for sid in student_ids if sid not in exist_student_ids]
if not target_student_ids:
return
# 批量查询学生总成绩,避免循环查库
all_student_scores = self.env['course.student.score'].search([
('course_id', '=', self.course_id.id),
('student_id', 'in', target_student_ids)
])
score_map = {rec.student_id.id: rec for rec in all_student_scores}
detail_vals_list = []
for stu_id in target_student_ids:
student_score = score_map.get(stu_id)
if not student_score:
continue
detail_vals_list.append({
'student_score_id': student_score.id,
'homework_id': self.id,
'score_item_id': score_item.id,
'score': 0.0,
'remark': f'{self.course_score_item_id.name}:{self.name}'
})
if detail_vals_list:
self.env['course.student.score.detail'].create(detail_vals_list)
def _auto_create_empty_submit(self):
"""仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录"""
@@ -138,11 +164,41 @@ class CourseHomework(models.Model):
batch_vals.append({
'homework_id': self.id,
'student_id': stu_id,
'course_score_item_id':self.course_score_item_id.id,
'state': 'unsubmit' # 默认未提交
})
# 批量创建空白提交记录
submit_model.create(batch_vals)
def _send_homework_notice_to_students(self):
"""发布作业后推送消息给教学班学生"""
self.ensure_one()
if not self.teaching_class_id:
return
# 获取当前教学班所有已选课学生
enrolls = self.env['course.teaching_class.enrollment'].search([
('teaching_class_id', '=', self.teaching_class_id.id),
('state', '=', 'enrolled')
])
student_users = enrolls.mapped('student_id.user_id').filtered(lambda u: u)
if not student_users:
return
# 消息内容
subject = f"新作业发布:{self.name}"
body = f"""
<p>课程:{self.course_id.name}</p>
<p>作业名称:{self.name}</p>
<p>截止时间:{self.deadline or '无'}</p>
<p>作业要求:{self.requirement or '无'}</p>
"""
# 发送消息(关联当前作业记录,学生在消息中心可直接跳转)
self.message_post(
subject=subject,
body=body,
partner_ids=student_users.mapped('partner_id').ids,
subtype_xmlid='mail.mt_note'
)
# ====================== 草稿/关闭作业 ======================
def action_draft(self):
self.state = 'draft'
@@ -269,4 +325,15 @@ class CourseHomework(models.Model):
def create(self, vals):
homework_record = super().create(vals)
# 草稿状态不执行自动生成提交记录,移到action_publish
target_a = self.env["course.score.item"].search([
("name", "=", "作业"),
("create_uid", "=", self.env.uid)
], limit=1)
if target_a:
# 将刚新建的B追加到A的多对多字段
target_a.write({
"homework_ids": [(4, homework_record.id)]
})
return homework_record
@@ -28,6 +28,7 @@ class CourseHomeworkSubmit(models.Model):
student_no = fields.Char(related="student_id.stu_num", string="学号", readonly=True)
student_class_id = fields.Many2one(related="student_id.class_id", string="班级", readonly=True)
student_major_id = fields.Many2one(related="student_id.major_id", string="专业", readonly=True)
course_score_item_id = fields.Many2one('course.score.item', string='作业类型')
student_phone = fields.Char(related="student_id.stu_phone", string="手机号", readonly=True)
submit_file = fields.Binary(string='提交文件', attachment=True)
submit_filename = fields.Char(string='文件名')
@@ -92,6 +93,7 @@ class CourseHomeworkSubmit(models.Model):
def create(self, vals):
submit_rec = super().create(vals)
self._check_submit_change_state(submit_rec)
return submit_rec
def write(self, vals):
+2 -2
View File
@@ -15,7 +15,7 @@ class CourseScoreItem(models.Model):
course_code = fields.Char(related="course_id.code", string="课程代码", readonly=True)
course_credit = fields.Float(related="course_id.credit", string="学分", readonly=True)
course_exam_type = fields.Selection(related="course_id.exam_type", string="考核方式", readonly=True)
homework_id = fields.Many2one('course.homework', string='作业', required=True, ondelete='cascade')
homework_id = fields.Many2one('course.homework', string='作业', ondelete='cascade')
sequence = fields.Integer(string="排序", default=10)
score_type = fields.Selection([
('homework', '作业'),
@@ -30,7 +30,7 @@ class CourseScoreItem(models.Model):
full_score = fields.Float(string='满分', default=100.0)
is_active = fields.Boolean(string='是否启用', default=True)
homework_ids = fields.Many2many('course.homework', string='关联作业')
course_student_score=fields.Many2one('course.student.score',string='学生成绩')
# 统计字段
student_count = fields.Integer(string='学生人数', compute='_compute_stats')
avg_score = fields.Float(string='平均分', compute='_compute_stats')
@@ -24,15 +24,16 @@ class CourseStudentScore(models.Model):
student_class_id = fields.Many2one( string="班级", related="student_id.class_id", readonly=True)
student_major_id = fields.Many2one( string="专业", related="student_id.major_id", readonly=True)
stu_name=fields.Char(related='student_id.stu_name',required=True, ondelete='cascade',string='学生姓名')
score_item_ids=fields.One2many('course.score.item','course_student_score',string='成绩类型')
score_ids = fields.One2many('course.student.score.detail', 'student_score_id', string='成绩明细')
teaching_class_id=fields.Many2one('course.teaching_class',string='教学班')
# 教学班相关只读关联字段
teaching_class_name = fields.Char(string="教学班名称", related="teaching_class_id.display_name", readonly=True)
teaching_class_semester = fields.Selection(string="学期", related="teaching_class_id.semester", readonly=True)
teaching_class_main_teacher = fields.Many2one('hr.employee', string="主讲教师",
related="teaching_class_id.main_teacher_id", readonly=True)
related="teaching_class_id.main_teacher_id", readonly=True)
total_score = fields.Float(string='综合成绩', compute='_compute_total_score', store=True)
course_score_item_id = fields.Many2one('course.score.item', string='成绩分项')
grade_level = fields.Selection([
('A', '优秀(A)'),
('B', '良好(B)'),
@@ -77,8 +78,8 @@ class CourseStudentScoreDetail(models.Model):
student_score_id = fields.Many2one('course.student.score', string='学生成绩', required=True, ondelete='cascade')
student_score_displayname=fields.Char(related='student_score_id.display_name',required=True, ondelete='cascade',string='显示名称')
# 关联成绩项目
score_item_id = fields.Many2one('course.score.item', string='成绩项目', required=True, ondelete='restrict')
score_item_id = fields.Many2one('course.score.item', string='成绩项目', ondelete='restrict')
homework_id=fields.Many2one('course.homework',string='作业')
# 得分
score = fields.Float(string='得分', help='该项目实际得分')
@@ -215,12 +215,32 @@ class CourseTeachingClassEnrollment(models.Model):
@api.model
def create(self, vals):
"""新增选课记录校验人数上限"""
"""新增选课记录校验人数上限 + 自动生成学生总成绩记录"""
res = super().create(vals)
cls = res.teaching_class_id
student = res.student_id
course = cls.course_id
# 1. 人数上限校验原有逻辑保留
valid_num = len(cls.enrollment_ids.filtered(lambda e: e.state == 'enrolled'))
if cls.max_students and valid_num > cls.max_students:
raise ValidationError("该教学班人数已达上限,无法继续添加学生!")
# 2. 查找是否已经存在该学生+该课程+该教学班的成绩主记录,避免重复创建
exist_score = self.env['course.student.score'].search([
('student_id', '=', student.id),
('course_id', '=', course.id),
('teaching_class_id', '=', cls.id)
], limit=1)
# 不存在则自动创建一条空成绩主记录
if not exist_score:
self.env['course.student.score'].create({
'course_id': course.id,
'student_id': student.id,
'teaching_class_id': cls.id
})
return res
@@ -6,7 +6,7 @@
<list>
<field name="name"/>
<field name="course_id"/>
<field name="chapter_id"/>
<!-- <field name="chapter_id"/>-->
<field name="total_score"/>
<field name="deadline"/>
<field name="submitted_count" sum="合计"/>
@@ -38,9 +38,10 @@
</div>
<group name="basic_info" string="基本信息">
<group>
<field name="teaching_class_id"/>
<field name="course_id" options="{'no_create': True}"/>
<field name="chapter_id" options="{'no_create': True}"/>
<field name="teaching_class_id"/>
<field name="course_score_item_id" />
<!-- <field name="chapter_id" options="{'no_create': True}"/>-->
<field name="total_score"/>
<field name="state" widget="statusbar"/>
</group>
@@ -45,17 +45,11 @@
</group>
</group>
<notebook>
<page string="作业明细">
<field name="score_ids" view_id="list_course_student_score_score_ids_om"/>
</page>
<page string="成绩明细">
<field name="score_ids">
<list editable="bottom">
<field name="score_item_id" options="{'no_create': True}"/>
<field name="score"/>
<field name="full_score" readonly="1"/>
<field name="weight" readonly="1"/>
<field name="completion_rate" widget="progressbar"/>
<field name="remark"/>
</list>
</field>
<field name="score_item_ids" view_id="list_course_student_score_item_om"/>
</page>
<page string="课程信息">
<group string="课程详情">
@@ -89,6 +83,74 @@
</field>
</record>
<record id="list_course_student_score_detail_ids_om" model="ir.ui.view">
<field name="name">list.course.student.score.detail.ids.om</field>
<field name="model">course.student.score.detail</field>
<field name="arch" type="xml">
<list editable="bottom">
<field name="display_name"/>
<field name="score"/>
<field name="full_score" readonly="1"/>
<!-- <field name="weight" readonly="1"/>-->
<!-- <field name="completion_rate" widget="progressbar"/>-->
<field name="remark"/>
</list>
</field>
</record>
<record id="list_course_student_score_item_om" model="ir.ui.view">
<field name="name">list.course.student.score.item.om</field>
<field name="model">course.score.item</field>
<field name="arch" type="xml">
<list>
<field name="name"/>
<field name="course_id"/>
<field name="weight"/>
<field name="full_score"/>
</list>
</field>
</record>
<!-- ==================== 学生成绩动作 ==================== -->
<record id="act_course_student_score" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
</record>
<!-- ==================== 按课程查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_course" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('course_id', '=', active_id)]</field>
<field name="context">{'default_course_id': active_id}</field>
</record>
<!-- ==================== 按教学班查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_teaching_class" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('teaching_class_id', '=', active_id)]</field>
<field name="context">{'default_teaching_class_id': active_id}</field>
</record>
<!-- ==================== 二级菜单 ==================== -->
<record id="menu_course_student_score" model="ir.ui.menu">
<field name="name">学生成绩</field>
<field name="parent_id" ref="menu_learning_center_root"/>
<field name="action" ref="act_course_student_score"/>
<field name="sequence">60</field>
</record>
<!-- ==================== 学生成绩明细列表视图 ==================== -->
<record id="view_course_student_score_detail_list" model="ir.ui.view">
@@ -148,16 +210,18 @@
</field>
</record>
<!-- ==================== 学生成绩动作 ==================== -->
<record id="act_course_student_score" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<record id="view_course_student_score_detail_search" model="ir.ui.view">
<field name="name">view.course.student.score.detail.search</field>
<field name="model">course.student.score.detail</field>
<field name="arch" type="xml">
<search>
<searchpanel>
<field name="score_item_id" select="multi"/>
</searchpanel>
</search>
</field>
</record>
<!-- ==================== 学生成绩明细动作 ==================== -->
<!-- ==================== 学生成绩明细动作 ==================== -->
<record id="act_course_student_score_detail" model="ir.actions.act_window">
<field name="name">成绩明细</field>
<field name="res_model">course.student.score.detail</field>
@@ -165,35 +229,7 @@
</record>
<!-- ==================== 按课程查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_course" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('course_id', '=', active_id)]</field>
<field name="context">{'default_course_id': active_id}</field>
</record>
<!-- ==================== 按教学班查看学生成绩的动作 ==================== -->
<record id="act_course_student_score_by_teaching_class" model="ir.actions.act_window">
<field name="name">学生成绩</field>
<field name="res_model">course.student.score</field>
<field name="view_mode">list,form</field>
<field name="view_id" ref="list_course_student_score"/>
<field name="domain">[('teaching_class_id', '=', active_id)]</field>
<field name="context">{'default_teaching_class_id': active_id}</field>
</record>
<!-- ==================== 二级菜单 ==================== -->
<record id="menu_course_student_score" model="ir.ui.menu">
<field name="name">学生成绩</field>
<field name="parent_id" ref="menu_learning_center_root"/>
<field name="action" ref="act_course_student_score"/>
<field name="sequence">60</field>
</record>
<!-- ==================== 成绩明细二级菜单 ==================== -->
<!-- ==================== 成绩明细二级菜单 ==================== -->
<record id="menu_course_student_score_detail" model="ir.ui.menu">
<field name="name">成绩明细</field>
<field name="parent_id" ref="menu_learning_center_root"/>
@@ -36,6 +36,8 @@
<group name="basic_info" string="基本信息">
<group>
<field name="homework_id" options="{'no_create': True}"/>
<field name="course_score_item_id" />
<field name="student_id" options="{'no_create': True}"/>
<field name="submit_time" readonly="1"/>
<field name="is_late" widget="boolean_toggle" readonly="1"/>
+12 -12
View File
@@ -104,18 +104,18 @@
</field>
</record>
<!-- <record id="view_course_syllabus_list" model="ir.ui.view">-->
<!-- <field name="name">course.syllabus.list</field>-->
<!-- <field name="model">course.syllabus</field>-->
<!-- <field name="arch" type="xml">-->
<!-- <list>-->
<!-- <field name="title"/>-->
<!-- <field name="syllabus_type" widget="badge"/>-->
<!-- <field name="version"/>-->
<!-- <field name="is_published" widget="boolean_toggle"/>-->
<!-- </list>-->
<!-- </field>-->
<!-- </record>-->
<record id="view_course_syllabus_list" model="ir.ui.view">
<field name="name">course.syllabus.list</field>
<field name="model">course.syllabus</field>
<field name="arch" type="xml">
<list>
<field name="title"/>
<field name="syllabus_type" widget="badge"/>
<field name="version"/>
<field name="is_published" widget="boolean_toggle"/>
</list>
</field>
</record>
<record id="view_course_chapter_resource_list" model="ir.ui.view">
<field name="name">course.chapter.resource.list</field>