diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a60b85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/learning_center/__manifest__.py b/learning_center/__manifest__.py index 22aa1ac..afb027e 100644 --- a/learning_center/__manifest__.py +++ b/learning_center/__manifest__.py @@ -25,7 +25,7 @@ 'views/view_course_teaching_class.xml', 'views/view_course_student_score.xml', 'views/view_course_homework_student.xml', - 'views/view_teach_class.xml', + # 'views/view_teach_class.xml', ], diff --git a/learning_center/models/__init__.py b/learning_center/models/__init__.py index 043df1a..1245d4b 100644 --- a/learning_center/models/__init__.py +++ b/learning_center/models/__init__.py @@ -7,4 +7,4 @@ from . import course_homework_submit from . import course_score_item from . import course_student_score from . import course_teaching_class -from . import teach_class \ No newline at end of file +# from . import teach_class \ No newline at end of file diff --git a/learning_center/models/__pycache__/__init__.cpython-312.pyc b/learning_center/models/__pycache__/__init__.cpython-312.pyc index 69332b4..d1f1900 100644 Binary files a/learning_center/models/__pycache__/__init__.cpython-312.pyc and b/learning_center/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_chapter.cpython-312.pyc b/learning_center/models/__pycache__/course_chapter.cpython-312.pyc deleted file mode 100644 index 6c803a3..0000000 Binary files a/learning_center/models/__pycache__/course_chapter.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/__pycache__/course_chapter_resource.cpython-312.pyc b/learning_center/models/__pycache__/course_chapter_resource.cpython-312.pyc deleted file mode 100644 index 1a09887..0000000 Binary files a/learning_center/models/__pycache__/course_chapter_resource.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/__pycache__/course_homework.cpython-312.pyc b/learning_center/models/__pycache__/course_homework.cpython-312.pyc index dce2bc4..d43fdd7 100644 Binary files a/learning_center/models/__pycache__/course_homework.cpython-312.pyc and b/learning_center/models/__pycache__/course_homework.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc b/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc index 076fe2e..bef74ce 100644 Binary files a/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc and b/learning_center/models/__pycache__/course_homework_submit.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/course_score_item.cpython-312.pyc b/learning_center/models/__pycache__/course_score_item.cpython-312.pyc deleted file mode 100644 index 26bd81a..0000000 Binary files a/learning_center/models/__pycache__/course_score_item.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/__pycache__/course_student_score.cpython-312.pyc b/learning_center/models/__pycache__/course_student_score.cpython-312.pyc deleted file mode 100644 index dba182a..0000000 Binary files a/learning_center/models/__pycache__/course_student_score.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/__pycache__/course_syllabus.cpython-312.pyc b/learning_center/models/__pycache__/course_syllabus.cpython-312.pyc deleted file mode 100644 index 25acc02..0000000 Binary files a/learning_center/models/__pycache__/course_syllabus.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc b/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc index 08725f7..4ff25bb 100644 Binary files a/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc and b/learning_center/models/__pycache__/course_teaching_class.cpython-312.pyc differ diff --git a/learning_center/models/__pycache__/learning_course.cpython-312.pyc b/learning_center/models/__pycache__/learning_course.cpython-312.pyc deleted file mode 100644 index ddb1005..0000000 Binary files a/learning_center/models/__pycache__/learning_course.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/__pycache__/teach_class.cpython-312.pyc b/learning_center/models/__pycache__/teach_class.cpython-312.pyc deleted file mode 100644 index 9652096..0000000 Binary files a/learning_center/models/__pycache__/teach_class.cpython-312.pyc and /dev/null differ diff --git a/learning_center/models/course_homework.py b/learning_center/models/course_homework.py index bfab775..ffcb0eb 100644 --- a/learning_center/models/course_homework.py +++ b/learning_center/models/course_homework.py @@ -1,5 +1,6 @@ from odoo import api, fields, models from odoo.exceptions import ValidationError +from datetime import datetime class CourseHomework(models.Model): _name = 'course.homework' @@ -13,8 +14,9 @@ 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='发布时间', default=fields.Datetime.now) + publish_time = fields.Datetime(string='发布时间') deadline = fields.Datetime(string='提交截止时间', required=True) late_deadline = fields.Datetime(string='补交截止时间') state = fields.Selection([ @@ -22,70 +24,242 @@ class CourseHomework(models.Model): ('published', '已发布'), ('closed', '已关闭'), ], string='状态', default='draft', tracking=True) - is_submit_open = fields.Boolean(string='提交通道开启', default=True) + is_submit_open = fields.Boolean(string='提交通道开启', compute='_compute_submit_channel', store=True) allow_late = fields.Boolean(string='允许补交', default=False) 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='提交记录') +<<<<<<< HEAD + score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项目') +======= + score_item_ids = fields.One2many('course.score.item', 'homework_id', string='成绩项') +>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb + # ====================== 新增:自动计算提交通道是否开启 ====================== + @api.depends('state', 'deadline') + def _compute_submit_channel(self): + now = fields.Datetime.now() + for record in self: + # 仅已发布、且当前时间未到截止时间,通道才开启 + if record.state == 'published' and record.deadline and record.deadline > now: + record.is_submit_open = True + else: + record.is_submit_open = False + + # ====================== 统计提交人数 ====================== def _compute_submit_stats(self): for record in self: - # 按教学班 + 选课状态筛选学生 domain = [('state', '=', 'enrolled')] if record.teaching_class_id: - # 优先按当前作业所属教学班统计(核心修正) domain.append(('teaching_class_id', '=', record.teaching_class_id.id)) else: - # 无教学班时,回退到整门课程统计 domain.append(('course_id', '=', record.course_id.id)) enrollments = self.env['course.teaching_class.enrollment'].search(domain) student_ids = enrollments.mapped('student_id') record.total_students = len(student_ids) - # 该作业所有提交记录 base_domain = [('homework_id', '=', record.id)] all_submit = self.env['course.homework.submit'].search(base_domain) - total_submit = len(all_submit) - deadline_val = record.deadline - if deadline_val: - # 按时提交 - on_time_submit = all_submit.filtered(lambda s: s.submit_time and s.submit_time <= deadline_val) - # 逾期提交 - late_submit = all_submit.filtered(lambda s: s.submit_time and s.submit_time > deadline_val) - record.submitted_count = len(on_time_submit) - record.late_count = len(late_submit) - else: - record.submitted_count = total_submit - record.late_count = 0 + # 已提交 = 状态submitted/graded + submit_ok = all_submit.filtered(lambda s: s.state in ['submitted', 'graded']) + late_submit = submit_ok.filtered(lambda s: s.is_late) - # 未提交 = 班级总人数 - 所有提交人数 - record.unsubmitted_count = record.total_students - total_submit + record.submitted_count = len(submit_ok) + record.late_count = len(late_submit) + # 未提交 = 总人数 - 已提交人数 + record.unsubmitted_count = record.total_students - len(submit_ok) + # ====================== 发布作业(核心逻辑修正) ====================== def action_publish(self): + self.ensure_one() + if self.state == 'published': + return self.state = 'published' self.publish_time = fields.Datetime.now() + # 1. 批量为本班学生生成空白未提交记录 + self._auto_create_empty_submit() +<<<<<<< HEAD + # 新增:自动生成对应作业的学生成绩明细 + self._auto_create_homework_score_detail() + # 2. 推送作业通知给学生 + self._send_homework_notice_to_students() + def _auto_create_homework_score_detail(self): + self.ensure_one() + # 修正字段名 course_score_item_id + if not self.teaching_class_id or not self.course_score_item_id: + return + 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_ids = enroll_records.mapped('student_id').ids + if not student_ids: + return + + # 查询当前作业+当前分项已存在明细 + 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) +======= + # 2. 推送作业通知给学生 + self._send_homework_notice_to_students() + + 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""" +

课程:{self.course_id.name}

+

作业名称:{self.name}

+

截止时间:{self.deadline or '无'}

+

作业要求:{self.requirement or '无'}

+ """ + # 发送消息(关联当前作业记录,学生在消息中心可直接跳转) + self.message_post( + subject=subject, + body=body, + partner_ids=student_users.mapped('partner_id').ids, + subtype_xmlid='mail.mt_comment' + ) +>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb + + def _auto_create_empty_submit(self): + """仅发布时执行:自动给教学班所有已选课学生生成空白未提交记录""" + self.ensure_one() + if not self.teaching_class_id: + return + # 获取当前教学班已注册学生 + enroll_records = self.env['course.teaching_class.enrollment'].search([ + ('teaching_class_id', '=', self.teaching_class_id.id), + ('state', '=', 'enrolled') + ]) + student_ids = enroll_records.mapped('student_id').ids + if not student_ids: + return + + # 过滤掉已经存在提交记录的学生,避免重复生成 + exist_student_ids = self.submit_ids.mapped('student_id').ids + need_create_stu = [sid for sid in student_ids if sid not in exist_student_ids] + if not need_create_stu: + return + + submit_model = self.env['course.homework.submit'] + batch_vals = [] + for stu_id in need_create_stu: + batch_vals.append({ + 'homework_id': self.id, + 'student_id': stu_id, +<<<<<<< HEAD + 'course_score_item_id':self.course_score_item_id.id, +======= +>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb + 'state': 'unsubmit' # 默认未提交 + }) + # 批量创建空白提交记录 + submit_model.create(batch_vals) + +<<<<<<< HEAD + 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""" +

课程:{self.course_id.name}

+

作业名称:{self.name}

+

截止时间:{self.deadline or '无'}

+

作业要求:{self.requirement or '无'}

+ """ + # 发送消息(关联当前作业记录,学生在消息中心可直接跳转) + self.message_post( + subject=subject, + body=body, + partner_ids=student_users.mapped('partner_id').ids, + subtype_xmlid='mail.mt_note' + ) + +======= +>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb + # ====================== 草稿/关闭作业 ====================== def action_draft(self): self.state = 'draft' def action_close(self): self.state = 'closed' + # 关闭作业强制关闭提交通道 self.is_submit_open = False + # ====================== 时间校验约束 ====================== @api.constrains('deadline', 'late_deadline') def _check_deadline(self): for record in self: if record.late_deadline and record.late_deadline < record.deadline: raise ValidationError('补交截止时间必须晚于提交截止时间') + # ====================== 学生提交相关计算字段 ====================== def _get_current_student(self): """获取当前登录的学生""" student = self.env['student.info'].search([('user_id', '=', self.env.uid)], limit=1) @@ -103,7 +277,7 @@ class CourseHomework(models.Model): my_submit_filename = fields.Char(string='文件名', compute='_compute_my_submit') my_submit_content = fields.Html(string='提交内容', compute='_compute_my_submit') - # 提交状态(用于前端显示) + # 前端展示提交状态 submit_state = fields.Selection([ ('not_submitted', '未提交'), ('submitted', '已提交'), @@ -111,69 +285,102 @@ class CourseHomework(models.Model): ('late', '逾期提交'), ], string='提交状态', compute='_compute_my_submit') - # 是否可以提交 + # 是否可以提交:严格依赖提交通道开启状态 can_submit = fields.Boolean(string='可以提交', compute='_compute_can_submit') - @api.depends('submit_ids') + @api.depends('submit_ids', 'submit_ids.student_id', 'submit_ids.state', 'submit_ids.is_late') def _compute_my_submit(self): + student = self._get_current_student() for record in self: - student = self._get_current_student() - if student: - submit = record.submit_ids.filtered(lambda s: s.student_id == student) - if submit: - record.my_submit_id = submit.id - record.my_submit_state = submit.state - record.my_submit_time = submit.submit_time - record.my_score = submit.score - record.my_grade_level = submit.grade_level - record.my_comment = submit.comment - record.my_submit_file = submit.submit_file - record.my_submit_filename = submit.submit_filename - record.my_submit_content = submit.submit_content - - # 计算提交状态 - if submit.state == 'graded': - record.submit_state = 'graded' - elif submit.is_late: - record.submit_state = 'late' - else: - record.submit_state = 'submitted' - else: - record.submit_state = 'not_submitted' - else: + if not student: + record.my_submit_id = False record.submit_state = 'not_submitted' + continue + submit = record.submit_ids.filtered(lambda s: s.student_id == student) + if not submit: + record.my_submit_id = False + record.submit_state = 'not_submitted' + continue + submit = submit[0] + record.my_submit_id = submit.id + record.my_submit_time = submit.submit_time + record.my_score = submit.score + record.my_grade_level = submit.grade_level + record.my_comment = submit.comment + record.my_submit_file = submit.submit_file + record.my_submit_filename = submit.submit_filename + record.my_submit_content = submit.submit_content + # 适配前端状态展示 + if submit.state == 'unsubmit': + record.submit_state = 'not_submitted' + elif submit.state == 'graded': + record.submit_state = 'graded' + elif submit.is_late: + record.submit_state = 'late' + else: + record.submit_state = 'submitted' + + @api.depends('state', 'is_submit_open', 'my_submit_state') def _compute_can_submit(self): for record in self: - # 作业已发布、提交通道开启、未截止、且未提交 - can = (record.state == 'published' and - record.is_submit_open and - record.deadline and - record.deadline > fields.Datetime.now() and - record.my_submit_state != 'submitted' and - record.my_submit_state != 'graded') + # 核心条件:提交通道必须开启,且未提交/未批改 + can = False + if record.is_submit_open and record.my_submit_state not in ['submitted', 'graded']: + can = True record.can_submit = can + # ====================== 提交作业按钮方法 ====================== def action_submit_homework(self): - """学生提交作业""" self.ensure_one() student = self._get_current_student() if not student: raise ValidationError('请先关联学生信息') - # 检查提交权限 + # 核心校验:通道关闭直接报错,禁止进入提交弹窗 if not self.can_submit: - raise ValidationError('当前无法提交作业') + raise ValidationError('当前提交通道已关闭,无法提交作业') - # 打开提交表单 - return { - 'type': 'ir.actions.act_window', - 'name': '提交作业', - 'res_model': 'course.homework.submit', - 'view_mode': 'form', - 'target': 'new', - 'context': { - 'default_homework_id': self.id, - 'default_student_id': student.id, - }, - } \ No newline at end of file + # 后续打开提交表单逻辑不变 + exist_submit = self.env['course.homework.submit'].search([ + ('homework_id', '=', self.id), + ('student_id', '=', student.id) + ], limit=1) + if exist_submit: + return { + 'type': 'ir.actions.act_window', + 'name': '提交作业', + 'res_model': 'course.homework.submit', + 'res_id': exist_submit.id, + 'view_mode': 'form', + 'target': 'new', + } + else: + return { + 'type': 'ir.actions.act_window', + 'name': '提交作业', + 'res_model': 'course.homework.submit', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_homework_id': self.id, 'default_student_id': student.id}, + } + + # 移除原create里自动生成空白提交的代码,草稿不生成 + def create(self, vals): + homework_record = super().create(vals) + # 草稿状态不执行自动生成提交记录,移到action_publish +<<<<<<< HEAD + + 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)] + }) +======= +>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb + return homework_record \ No newline at end of file diff --git a/learning_center/models/course_homework_submit.py b/learning_center/models/course_homework_submit.py index 681e245..589f2d6 100644 --- a/learning_center/models/course_homework_submit.py +++ b/learning_center/models/course_homework_submit.py @@ -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='文件名') @@ -38,6 +39,7 @@ class CourseHomeworkSubmit(models.Model): grader_id = fields.Many2one('res.users', string='批改人') grade_time = fields.Datetime(string='批改时间') state = fields.Selection([ + ('unsubmit', '未提交'), ('submitted', '已提交'), ('graded', '已批改'), ], string='状态', default='submitted') @@ -87,6 +89,36 @@ class CourseHomeworkSubmit(models.Model): else: record.grade_level = False + @api.model + def create(self, vals): + submit_rec = super().create(vals) + self._check_submit_change_state(submit_rec) +<<<<<<< HEAD + +======= +>>>>>>> 0b2a587b69524dafde97f76bad27721afebdcddb + return submit_rec + + def write(self, vals): + res = super().write(vals) + for rec in self: + self._check_submit_change_state(rec) + return res + + def _check_submit_change_state(self, submit_rec): + """统一提取状态判断逻辑,create和write共用""" + # 仅空白未提交状态才自动切换 + if submit_rec.state != 'unsubmit': + return + # 判断:有提交文件 或者 填写了提交内容 = 完成提交 + has_file = bool(submit_rec.submit_file) + has_content = bool(submit_rec.submit_content) + if has_file or has_content: + submit_rec.write({ + 'state': 'submitted', + 'submit_time': fields.Datetime.now() + }) + # class CourseHomeworkSubmitBatchGrade(models.TransientModel): # _name = 'course.homework.submit.batch.grade' # _description = '作业批量批改向导' diff --git a/learning_center/models/course_score_item.py b/learning_center/models/course_score_item.py index d04617c..c5982ec 100644 --- a/learning_center/models/course_score_item.py +++ b/learning_center/models/course_score_item.py @@ -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') diff --git a/learning_center/models/course_student_score.py b/learning_center/models/course_student_score.py index e01ba2e..3e8157d 100644 --- a/learning_center/models/course_student_score.py +++ b/learning_center/models/course_student_score.py @@ -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='该项目实际得分') diff --git a/learning_center/models/course_teaching_class.py b/learning_center/models/course_teaching_class.py index 515af67..c522eda 100644 --- a/learning_center/models/course_teaching_class.py +++ b/learning_center/models/course_teaching_class.py @@ -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 @@ -288,4 +308,7 @@ class CourseTeachingClassEnrollmentWizard(models.TransientModel): if create_list: self.env['course.teaching_class.enrollment'].create(create_list) - return {'type': 'ir.actions.act_window_close'} \ No newline at end of file + return {'type': 'ir.actions.act_window_close'} + + + diff --git a/learning_center/models/teach_class.py b/learning_center/models/teach_class.py index 454f957..384b170 100644 --- a/learning_center/models/teach_class.py +++ b/learning_center/models/teach_class.py @@ -10,4 +10,5 @@ class TeachClass(models.Model): stu_name=fields.Char(related='student_id.stu_name',string='姓名') stu_sex = fields.Selection(related='student_id.stu_sex', string="性别") stu_phone = fields.Char(related='student_id.stu_phone', string="手机号") - stu_grade_xx = fields.Many2one(related='student_id.stu_grade_xx', string="年级") \ No newline at end of file + stu_grade_xx = fields.Many2one(related='student_id.stu_grade_xx', string="年级") + diff --git a/learning_center/security/ir.model.access.csv b/learning_center/security/ir.model.access.csv index 41f39ec..f137695 100644 --- a/learning_center/security/ir.model.access.csv +++ b/learning_center/security/ir.model.access.csv @@ -11,8 +11,6 @@ access_course_syllabus_sys,course.syllabus.access.sys,model_course_syllabus,base access_course_teaching_class_sys,course.teaching.class.access.sys,model_course_teaching_class,base.group_system,1,1,1,1 access_course_teaching_class_enrollment_sys,course.teaching.class.enrollment.access.sys,model_course_teaching_class_enrollment,base.group_system,1,1,1,1 access_course_teaching_class_enrollment_wizard_sys,course.teaching.class.enrollment.access.sys,model_course_teaching_class_enrollment_wizard,base.group_system,1,1,1,1 -access_teach_class_teacher,teach.class.access.teacher,model_teach_class,edu_base.group_teacher,1,1,0,0 -access_teach_class_sys,teach.class.access.sys,model_teach_class,base.group_system,1,1,1,1 access_course_teaching_class_teacher,course.teaching.class.access.teacher,model_course_teaching_class,edu_base.group_teacher,1,0,0,0 access_course_teaching_class_enrollment_teacher,course.teaching.class.enrollment.access.teacher,model_course_teaching_class_enrollment,edu_base.group_teacher,1,0,0,0 access_course_homework_teacher,course.homework.access.teacher,model_course_homework,edu_base.group_teacher,1,1,1,0 diff --git a/learning_center/views/view_course_homework.xml b/learning_center/views/view_course_homework.xml index 28cf3db..536c112 100644 --- a/learning_center/views/view_course_homework.xml +++ b/learning_center/views/view_course_homework.xml @@ -6,7 +6,7 @@ - + @@ -38,9 +38,10 @@ - - + + + diff --git a/learning_center/views/view_course_student_score.xml b/learning_center/views/view_course_student_score.xml index 4b7f81b..c0a5c26 100644 --- a/learning_center/views/view_course_student_score.xml +++ b/learning_center/views/view_course_student_score.xml @@ -45,17 +45,11 @@ + + + - - - - - - - - - - + @@ -89,6 +83,74 @@ + + list.course.student.score.detail.ids.om + course.student.score.detail + + + + + + + + + + + + + + + list.course.student.score.item.om + course.score.item + + + + + + + + + + + + + + 学生成绩 + course.student.score + list,form + + + + + + 学生成绩 + course.student.score + list,form + + [('course_id', '=', active_id)] + {'default_course_id': active_id} + + + + + 学生成绩 + course.student.score + list,form + + [('teaching_class_id', '=', active_id)] + {'default_teaching_class_id': active_id} + + + + + 学生成绩 + + + 60 + + + + @@ -148,16 +210,18 @@ - - - - 学生成绩 - course.student.score - list,form - + + view.course.student.score.detail.search + course.student.score.detail + + + + + + + - - + 成绩明细 course.student.score.detail @@ -165,35 +229,7 @@ - - - 学生成绩 - course.student.score - list,form - - [('course_id', '=', active_id)] - {'default_course_id': active_id} - - - - - 学生成绩 - course.student.score - list,form - - [('teaching_class_id', '=', active_id)] - {'default_teaching_class_id': active_id} - - - - - 学生成绩 - - - 60 - - - + 成绩明细 diff --git a/learning_center/views/view_course_teaching_class.xml b/learning_center/views/view_course_teaching_class.xml index 772069e..b9b89b3 100644 --- a/learning_center/views/view_course_teaching_class.xml +++ b/learning_center/views/view_course_teaching_class.xml @@ -204,9 +204,14 @@ + + 我的班级 + + + - 教学班管理 - + 我的班级 + 15 diff --git a/learning_center/views/view_homework_submit.xml b/learning_center/views/view_homework_submit.xml index 217931d..19e0e7e 100644 --- a/learning_center/views/view_homework_submit.xml +++ b/learning_center/views/view_homework_submit.xml @@ -36,6 +36,8 @@ + + diff --git a/learning_center/views/view_learning_course.xml b/learning_center/views/view_learning_course.xml index aecf80c..e1f7692 100644 --- a/learning_center/views/view_learning_course.xml +++ b/learning_center/views/view_learning_course.xml @@ -104,18 +104,18 @@ - - - - - - - - - - - - + + course.syllabus.list + course.syllabus + + + + + + + + + course.chapter.resource.list diff --git a/permission_manager/__init__.py b/permission_manager/__init__.py new file mode 100644 index 0000000..43559cd --- /dev/null +++ b/permission_manager/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/permission_manager/__manifest__.py b/permission_manager/__manifest__.py new file mode 100644 index 0000000..abb3074 --- /dev/null +++ b/permission_manager/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': 'Permission Manager', + 'version': '18.0.1.0.0', + 'category': 'Tools', + 'summary': '权限管理', + 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + 'views/role_views.xml', + ], + 'installable': True, +} \ No newline at end of file diff --git a/permission_manager/__pycache__/__init__.cpython-312.pyc b/permission_manager/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..d6127cd Binary files /dev/null and b/permission_manager/__pycache__/__init__.cpython-312.pyc differ diff --git a/permission_manager/models/__init__.py b/permission_manager/models/__init__.py new file mode 100644 index 0000000..8108826 --- /dev/null +++ b/permission_manager/models/__init__.py @@ -0,0 +1 @@ +from . import permission_role \ No newline at end of file diff --git a/permission_manager/models/__pycache__/__init__.cpython-312.pyc b/permission_manager/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..ca83b56 Binary files /dev/null and b/permission_manager/models/__pycache__/__init__.cpython-312.pyc differ diff --git a/permission_manager/models/__pycache__/permission_role.cpython-312.pyc b/permission_manager/models/__pycache__/permission_role.cpython-312.pyc new file mode 100644 index 0000000..12213e5 Binary files /dev/null and b/permission_manager/models/__pycache__/permission_role.cpython-312.pyc differ diff --git a/permission_manager/models/permission_role.py b/permission_manager/models/permission_role.py new file mode 100644 index 0000000..c3b26dc --- /dev/null +++ b/permission_manager/models/permission_role.py @@ -0,0 +1,134 @@ +from odoo import api, fields, models + +class PermissionRole(models.Model): + _name = 'permission.role' + _description = '权限角色' + _rec_name = 'name' + _order = 'sequence, id' + name = fields.Char(string='角色名称', required=True) + code = fields.Char(string='角色代码', required=True, help='如:student, teacher, admin') + sequence = fields.Integer(string='序号', default=10) + description = fields.Text(string='角色描述') + active = fields.Boolean(string='启用', default=True) + # 角色类型 + role_type = fields.Selection([ + ('student', '学生'), + ('teacher', '教师'), + ('admin', '管理员'), + ('custom', '自定义'), + ], string='角色类型', default='custom') + # 关联的权限组 + group_ids = fields.Many2many('res.groups', string='权限组') + # 可见菜单 + menu_ids = fields.Many2many('ir.ui.menu', string='可见菜单') + # 用户 + user_ids = fields.Many2many('res.users', string='用户') + user_count = fields.Integer(string='用户数', compute='_compute_user_count') + # 统计字段 + group_count = fields.Integer(string='权限组数', compute='_compute_group_count') + menu_count = fields.Integer(string='菜单数', compute='_compute_menu_count') + + @api.depends('user_ids') + def _compute_user_count(self): + for record in self: + record.user_count = len(record.user_ids) + + @api.depends('group_ids') + def _compute_group_count(self): + for record in self: + record.group_count = len(record.group_ids) + + @api.depends('menu_ids') + def _compute_menu_count(self): + for record in self: + record.menu_count = len(record.menu_ids) + + def action_assign_users(self): + """分配用户""" + return { + 'type': 'ir.actions.act_window', + 'name': '分配用户', + 'res_model': 'res.users', + 'view_mode': 'list,form', + 'target': 'new', + 'domain': [('id', 'not in', self.user_ids.ids)], + 'context': { + 'default_role_ids': [(4, self.id)], + }, + } + + def action_view_users(self): + """查看角色下的用户""" + return { + 'type': 'ir.actions.act_window', + 'name': f'角色用户 - {self.name}', + 'res_model': 'res.users', + 'view_mode': 'list,form', + 'domain': [('id', 'in', self.user_ids.ids)], + } + + def action_copy_permissions(self): + """复制权限(从其他角色)""" + return { + 'type': 'ir.actions.act_window', + 'name': '复制权限', + 'res_model': 'permission.copy.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': {'default_target_role_id': self.id}, + } + + +class ResUsersInherit(models.Model): + _inherit = 'res.users' + + role_ids = fields.Many2many('permission.role', string='权限角色') + role_count = fields.Integer(string='角色数', compute='_compute_role_count') + + @api.depends('role_ids') + def _compute_role_count(self): + for record in self: + record.role_count = len(record.role_ids) + + def action_view_roles(self): + """查看用户角色""" + return { + 'type': 'ir.actions.act_window', + 'name': '用户角色', + 'res_model': 'permission.role', + 'view_mode': 'list,form', + 'domain': [('id', 'in', self.role_ids.ids)], + } + + def action_sync_permissions(self): + """同步权限:将角色的权限组和菜单同步到用户""" + for user in self: + groups = user.role_ids.mapped('group_ids') + menus = user.role_ids.mapped('menu_ids') + user.write({ + 'groups_id': [(6, 0, groups.ids)], + }) + # 菜单权限需要单独处理 + for menu in menus: + # 这里可以添加自定义菜单权限逻辑 + pass + + +class PermissionCopyWizard(models.TransientModel): + _name = 'permission.copy.wizard' + _description = '复制权限向导' + + target_role_id = fields.Many2one('permission.role', string='目标角色', readonly=True) + source_role_id = fields.Many2one('permission.role', string='源角色', required=True) + copy_groups = fields.Boolean(string='复制权限组', default=True) + copy_menus = fields.Boolean(string='复制菜单权限', default=True) + + def action_copy(self): + self.ensure_one() + vals = {} + if self.copy_groups: + vals['group_ids'] = [(6, 0, self.source_role_id.group_ids.ids)] + if self.copy_menus: + vals['menu_ids'] = [(6, 0, self.source_role_id.menu_ids.ids)] + self.target_role_id.write(vals) + return {'type': 'ir.actions.act_window_close'} \ No newline at end of file diff --git a/permission_manager/security/ir.model.access.csv b/permission_manager/security/ir.model.access.csv new file mode 100644 index 0000000..e90c789 --- /dev/null +++ b/permission_manager/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_permission_role,permission.role,model_permission_role,base.group_system,1,1,1,1 +access_res_users,res.users.access,model_res_users,base.group_system,1,1,1,1 +access_permission_copy_wizard,permission.copy.wizard.access,model_permission_copy_wizard,base.group_system,1,1,1,1 \ No newline at end of file diff --git a/permission_manager/views/role_views.xml b/permission_manager/views/role_views.xml new file mode 100644 index 0000000..1cb19ad --- /dev/null +++ b/permission_manager/views/role_views.xml @@ -0,0 +1,166 @@ + + + + + + permission.role.list + permission.role + + + + + + + + + + + + + + + permission.role.form + permission.role + +
+ +
+

+ +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+ + + + permission.role.search + permission.role + + + + + + + + + + + + + + + + 角色管理 + permission.role + list,form + + + + + + permission.copy.wizard.form + permission.copy.wizard + +
+ + + + + + + +
+
+
+
+
+
+ + + + res.users.form.inherit + res.users + + + + + + + +
+
+
+
+
+
+
+ + + + + + + 权限管理 + 100 + + + + + + 角色管理 + + + 10 + + + +
+ diff --git a/student_organization/models/__pycache__/student_info.cpython-312.pyc b/student_organization/models/__pycache__/student_info.cpython-312.pyc index e7fb7d1..bf9112b 100644 Binary files a/student_organization/models/__pycache__/student_info.cpython-312.pyc and b/student_organization/models/__pycache__/student_info.cpython-312.pyc differ diff --git a/student_organization/models/student_info.py b/student_organization/models/student_info.py index ca54486..b7b06ca 100644 --- a/student_organization/models/student_info.py +++ b/student_organization/models/student_info.py @@ -1,4 +1,6 @@ from odoo import api,models,fields +from odoo.exceptions import ValidationError + class StudentInfo(models.Model): _name="student.info" @@ -24,7 +26,7 @@ class StudentInfo(models.Model): stu_begin_date = fields.Date(string="入学日期", required=True, tracking=True) stu_end_date = fields.Date(string="毕业日期") stu_grade_xx = fields.Many2one('basic.grade.xx',string="年级", tracking=True,domain = lambda self: self.env['basic.grade.xx']._get_current_year_range_domain()) - stu_grade_bx=fields.Many2one('basic.grade.xx',string='年级',readonle=1) + stu_grade_bx=fields.Many2one('basic.grade.xx',string='年级',readonly=1) stu_status = fields.Selection([ ('studying', '在读'), ('graduated', '已毕业'), @@ -42,13 +44,57 @@ class StudentInfo(models.Model): major_name = fields.Char(string="专业名称", related='major_id.name', store=True) department_id = fields.Many2one('student.organization', string="院系",store=True,domain="[('org_type', '=', 'college')]") + # 新增:关联系统登录用户(解决之前作业通知报错缺少user_id的问题) + user_id = fields.Many2one('res.users', string="系统登录账号", ondelete='restrict') - # user_id = fields.Many2one('res.users', string="系统用户", ondelete='restrict') @api.depends('parent_path') def _compute_org_path(self): for record in self: if record.class_id: - # 直接使用班级的 parent_path + 班级ID record.org_path = f"{record.class_id.parent_path}{record.class_id.id}/" else: - record.org_path = False \ No newline at end of file + record.org_path = False + + @api.model + def create(self, vals): + # 1. 先校验学号,提前取出学号避免多次读取 + stu_num = vals.get('stu_num', '').strip() + new_user = None + + # 2. 学号不为空才执行创建用户逻辑 + if stu_num: + # 校验学号对应的登录账号是否已存在 + exist_user = self.env['res.users'].search([('login', '=', stu_num)], limit=1) + if not exist_user: + # 获取两个权限组,并双重校验存在性 + internal_group = self.env.ref('base.group_user', raise_if_not_found=False) + student_group = self.env.ref('edu_base.group_student', raise_if_not_found=False) + + if not internal_group: + raise ValidationError("系统内置内部用户组 base.group_user 缺失,无法创建登录账号!") + if not student_group: + raise ValidationError("权限组 edu_base.group_student 不存在,请先创建该学生权限组!") + + # 组装用户参数,优先取学生填写的邮箱,无则自动生成 + stu_email = vals.get('stu_email') + if not stu_email: + stu_email = f"{stu_num}@edu.local" + + user_vals = { + 'name': vals.get('stu_name', ''), + 'login': stu_num, + 'password': stu_num, + 'email': stu_email, + 'phone': vals.get('stu_phone', ''), + # 同时分配后台登录组 + 学生业务权限组 + 'groups_id': [(6, 0, [student_group.id, internal_group.id])], + } + # 先创建用户,事务内失败会整体回滚 + new_user = self.env['res.users'].create(user_vals) + + # 3. 创建学生档案,如果已生成用户则绑定user_id + if new_user: + vals['user_id'] = new_user.id + student_info = super().create(vals) + + return student_info \ No newline at end of file diff --git a/student_organization/views/view_student_info.xml b/student_organization/views/view_student_info.xml index 4cfd0ec..4857473 100644 --- a/student_organization/views/view_student_info.xml +++ b/student_organization/views/view_student_info.xml @@ -3,7 +3,7 @@ student.info.list student.info - +