From 45fbe4fbbafcbd4becb4f1ab9521f5bad9d5a45e Mon Sep 17 00:00:00 2001 From: liuhaoran Date: Sat, 20 Jun 2026 19:32:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../course_homework.cpython-312.pyc | Bin 14111 -> 16920 bytes .../course_homework_submit.cpython-312.pyc | Bin 7134 -> 7252 bytes .../course_score_item.cpython-312.pyc | Bin 3797 -> 3923 bytes .../course_student_score.cpython-312.pyc | Bin 7795 -> 8112 bytes .../course_teaching_class.cpython-312.pyc | Bin 16823 -> 17457 bytes learning_center/models/course_homework.py | 113 +++++++++--- .../models/course_homework_submit.py | 2 + learning_center/models/course_score_item.py | 4 +- .../models/course_student_score.py | 9 +- .../models/course_teaching_class.py | 22 ++- .../views/view_course_homework.xml | 7 +- .../views/view_course_student_score.xml | 132 +++++++++----- .../views/view_homework_submit.xml | 2 + .../views/view_learning_course.xml | 24 +-- permission_manager/__init__.py | 1 + permission_manager/__manifest__.py | 12 ++ .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 191 bytes permission_manager/models/__init__.py | 1 + .../__pycache__/__init__.cpython-312.pyc | Bin 0 -> 207 bytes .../permission_role.cpython-312.pyc | Bin 0 -> 7174 bytes permission_manager/models/permission_role.py | 134 ++++++++++++++ .../security/ir.model.access.csv | 4 + permission_manager/views/role_views.xml | 166 ++++++++++++++++++ 23 files changed, 540 insertions(+), 93 deletions(-) create mode 100644 permission_manager/__init__.py create mode 100644 permission_manager/__manifest__.py create mode 100644 permission_manager/__pycache__/__init__.cpython-312.pyc create mode 100644 permission_manager/models/__init__.py create mode 100644 permission_manager/models/__pycache__/__init__.cpython-312.pyc create mode 100644 permission_manager/models/__pycache__/permission_role.cpython-312.pyc create mode 100644 permission_manager/models/permission_role.py create mode 100644 permission_manager/security/ir.model.access.csv create mode 100644 permission_manager/views/role_views.xml diff --git a/learning_center/models/__pycache__/course_homework.cpython-312.pyc b/learning_center/models/__pycache__/course_homework.cpython-312.pyc index d43fdd7c0307165e30aa081eaf9153931b75d0f6..ed50c02eb879c04f20cc56e591847d9f26ab514d 100644 GIT binary patch delta 5090 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 WntIf%^T17qZaa9}#|*Y5)&Bu^(DMrb delta 3141 zcmaJ@Yj6|S72d0tC1G1OwhWStWg#JXEh8-X5rZGt4g>-=kC>3uxDkqW!^oqDxhoay z(Ap{P#7tnoy+gnZEltV~D9jYl57I}{G}H8zVT#i-&}}khIzVXqBMvPy4KwZZoGS_J z{^? zFxJY-o0hVMvKs%>C^iCs@!qSWS4OD^4q^!v;YT$@Mj%c})x;!P&uYtu7rB5JMZvULqRUh!RVR1h^=XgMg0M%64Ql?;`` zsiaJKhH!>x4T6&$s9edeRjw$;w-thC|EUZaHX~yzYqf4J4$K+@H;j!u-t64+ZJIJa z8e`nlhu~$~RywkMT z;VS9B_YWwqOu@iuQJ&%a;QuecN z`&!6O_G{nK0qZTR?ZivdFWt1(&06cOSBi52sdwCRIM1w}S$)zECoeyB&UAk42%cMi z-5vs76Y_34{Id@K$ByQ6nd|lz;I^9PO!~57_JcaH&Oc`oO!kL@-el$}k79cko+2P{ z&j0ahk~xeGN%=mc;`w5ll7+2F>7?3+;qd}`|^Q9aJ)d2VzB$5+Vpgr z?gOt5kR`<`_GO*3Jcz!xaIXNkLPRl8$&i6TObEBE)l<47P#BzEHYqk%@#(Au;X=FL zih=4k(GCY1?UqgH4x4Y+xTkbCj1I7`tpAfcfo9m5)BqrAc&7ONQ1C*j#Q+xjo5^Xm z!QT!WR^F^y1#bBkzI*k` zy`#tO{P--`HnK-6x0Bu3zQBhXGRpU5ofitc0ggSDaE$l+6pL#r;z_fI;Okk!(}XyGd0 zXFb0|SX=1r5YP8h=t%b@UC=7LcXI*iKnnfRoeOb4D{k_VpJkhx?8LxBt7QYtM>_+c zwF8>H7Jlhb0M#6gB=fHYK6Wjyg{lRQ@hb3R{X&K95Mw!k*Fbn4J2tEs8Vo@x0d zvEwl128gd&K1CFH3LAgw#4z?mZY`ZaY6jsHfLatyCDZc$v{;mzwtXnUwxH7pj|KHhgzJUA)bLJak7cFUd`VG_KUW^?7|SESdqemU&Hr^7l`BJgpe*sghSc`!V%c| z2S{O;d|h)uGayjj4;odkWJY94BVd3fVD@&0-4qT-;}Jy(hn0Nt^ZgNFgY9--2`nWA zufcy!2%p%UGrpO?xys+$15=jUmYVCv8ouYmUS{YREBgwlsmA_Dax{dOUp+h4;Th)_ zAKq@cX~DTsO#^aVj;3R&B)=imqTW<0E=Q8QpsG0%kEiy9Z*t?-q#R1XJLzuVsinN#!uXd3_YY-lolD2JqIN$*AHpy~901xTl~mF+5=$yH zgFEig=3A_YG$aYJQG7IN0tMGhfJpE0oCclbt{5&3mX8fJHo7IHF8bJb}(I>tLv0j4}^q(Dd58fwiASOE5XVw5T{$uQ)lsC{-`BBsI5` z2WZffy>p)InDu<)?x*v*H5rRQ31qSzSJ>v+TaIwKU0{*etR|()$j`zm^_iK0jsF7^koARU ha+0(!W9j4#(vqS@jEww1O*S)azA}K=MG`=#0szh+B-eijOMgM6Lo+PwMHNd=0tC9XN=gwIj<(Xc<0+zjBWfGxv5?;)i-5x^bDnbsv(iY)tcGxu&(^U;)W;I&6D_ zc~)=t3$R3|9jg#wSB^=bzKVOeV8zNNu!Bl3$YN3DyGV-VjKH%Gi9S?K_}93R{#RC= fJY+-wcpwX)-@*8V3jpotC>LNF)&2QGBKobWJy4H z$YhFYiW-tSIj}k{WEBv0ih7F18qLXm%*y;)AQs$Yg~=0`B>A;b#1uhdo;oOEN??=S zu$ZiyqL-o%bOSR)E=ncEpoJkyHN_A}s#O|k8gG8YJcE%(lc|UksG^7mNNlcR6=zhh z5=qW4Ehh08>YIhRZ5YLF zLG{E#^~8hqO!nsqWjsE4DbF#M$}2*VlZ$xk#cxPz%n+L|KU4mKru~7sE0SRsSR@{Z WC{6yxJCiYZ@&rB!#<0mN`E&p)p>2Qw delta 293 zcmcaCcU6}6G%qg~0}#~i(atKL$UB+QX5+#nMt1fT-ZgxyStfHZDY8nXNUxEZtjDCv zD!Yg=MQ(BglRBgP4*B9t-t9&bJC2Q~&#<;f*{GZ}*>^YKeChE7)J*8u>(o?tOnJuBq0qJ3Bi#_T0kp!bRtT^(TJaK>dy{YuC_w2*v3IL9*g-HCQn~6vw4J zROOwv2#zw1Z)rXByWLL5ARfY|s<0L5rSBS?ju;-^q52?Scn0smdo|tIs^O=%kkb*z z`?p85QxlPZ8eViq>lcsA_b2MsPxRh7VliDi0fQsV8TXLA6#k z-ZYFjvc)nf<)Wo*k>sNJ>D*+a>!Fk`O_yk`O zi0OGcYx=`cesp&4(%YM#d|u1!O7Vdx%h)`@fD8hA0oV|*1h5#uFuiLopa?x?2{W9r ze1-aJhdO)E?uJ$Cng&DZJT8$Dnz4@iRhIO~A6V8S$U&$Vq2F42(Ei$=)`LhAdgtSp z)djWQ7q9Odt-qRG6Y$&^4ciMH$xMCXbkl(P2>saZ64#Z1#rU$iq}D^p+vnGm*Xl-j zovzw3+Mqs1vs0`}TXS&09G>XLa@ASk>95Fdb77_G*8g*=3uY~Na1S=I`FSh)L`2Y? zKJ0%?mv}B`Dnx0v0$Y*# z>2J~*l%Q{P|KQ%sq)o(t>|qczl00y2d6INg?r%TI-p|IEpI-WbY(v>vMZSaDwSYW* z%Qc2h(L1gS$VXpST-g^P`6$3K02m874qyg=J562!umX$%gaO6@FoT9!{{Xg+l=UuYqSok@nLAJX-p{NG&hovK@Rx=%HLD@81O} zQK>#mTk~zuws|4_ub2?3n{M}aWM7IaMexGVU9w16B~L+d>9_2k(vZ&A$X6Rf94vsU z4^*^$lFk?1nkLyCNA+hQ~(}kvLNWU^TuXzU)V%hv--;FD$LDo&HcVul^Hh@)S5B2WI+HrcunoMn)@aFjw za^Q$yRTfKqId3r#Yop{!7v~(#t@pu@RWufk%!PyT@-HOcI}H>J#?5z@N87QNwn9%+ zfyXl)^f#|Lj>o51LLE*{Q8ktv#%u3Yli{&(wmU1^DhL^YnyLuthxw{2@{APG+(B(T z!Lg(|0mSZt0eecCs^DFJy?J|md2#*8@|TCV;FG;x^z$Hyj%m^cR~_%&`}lGLFMzdh z_(euhw;?(puGEtV+*J;dbJ@4bYogcU6dt%vktlq3U9a@A9<&}s9OHnP+c}3*W&Mb9 z1pXLo0AWI$Kc=Mjb+UKX#I?a6dcmk<%RB}`&Vuai@@nqw x6@D8!C*pK!TJ##S&4$awZ@Eozl7 zS6UF-K)$KkMY*859bG2_)!)uI3JY{nJmHJ@yG6Vf)))ah$$kCwX0fXBQ{o8-HSp4p$j@r>mE%q z{W+R6JiS6vkeZV$5aLO*-L7J5R4fXDHP&Fk2mCpd_eWe(wTW{Z5^Koco_Vt?swy5F z`egjbnfn97zom|kfBG?qzH3z9zjpS)g^T0YPmt2-%Z>PTx*l%?Y$9C?*P*-Q^1`-* zcpX?^lQA?L&H~sbzem+sq-esS?oiy#=VzEBOuJ2rMoPp)tY`yR^v9vpas0p(0JSS0B8hIrAnprE zai0{3VruvK0%5--`FxU|2CA5P`Y;p)-yjETJSrZM&uS`cx#^ZcPui0ba>eGsvUFKW zJE`R?cJf2bih~nc&gK~GOm~ht>&C20Zdx11tc^KS=}>J(7%9jWj2f$QCi{?UsAqWp z$^D~7cg|Fiab={Do@~#k(K8{an+hg$-1L$OJ!f0_m{Z%#DZ`^euE>!y7WJ<=yk^u= zHD;{7X{;SH*5=Aw!#hsy$d%6-?mF3(n=^mJu{FE(k)WUqW3=6^+PiXP5OtTP%B@ddKiYc~voCp)9@Q>6gLEAOw_wp*ZUX}m!u10o;rsOftX{Sx9sA73ym=2_i zEo67?L-dxse(@^RESAy+I&qfIcNH_RL|P%*0djH4vRc+A{V+ZbfLn;&c8b;?i;0n7 zzGomwdLcr$;P1%H`o;8K+gRT~i!1lnpI4)!q@khC!nzoG7k>?aZ{yqKNJIJZ{0CwD zGlkoR@6eZ`ZV!b;)|rYb7R)F43po8JDRQr2^^zC*mcw^pvHq2P0WlU2MI&qr-Nvcx zVEsM7ucWQ9M!?^L-b~(a>_ArepT>)bIK8FYSv4~-#SYvED0TyDSA`;eydVD#7AUu( zgDlwv*ax;JfX@NYA^Gau0tI+NoXrxtMM$&P_6Hp+s!$XQAha=mB3r|lwd z8Ab^s0l_>V7*Sb_Z&4DL8%LIq=m$QK;69KzH?J%vMvU=;FQF#J59;~f)&#vtf9H8S z&v|m5^K^QWU3-aD->RxwW)a`dpN94y>zb~1c-djS#v8P;o%*A`V~p+5Kl5i88`0Zq z-(a-~5gM@bq*%BDi~=L{ZC#NKlh)v)AL~z=(o|Vr(|DvL=`)Qd7#lT%hF8q92}pWx z(+G!UKvw3-${j*kQ7JoF8ZjYv3WPd zr+~eH42%K$fCF^5WxdGfZ|z#E!k_^h$O99=ivor{lT9jVUIPQ^yfr4VA$=z3XKWjN zv39Ru`?^tfozAbDYdDP!&Ro6_SLKYtAIF|bU=JN>e{+Rhh&J)N_ocuEcF}^PO5Z6v zMLL3&dB?woqc)my`>gxjG~Ll=s}SnJKA*%Jj+RB|fz2YXJMSUydcVWE-4d`BT4^xU zM(09(?k7bWJ`6lfUxkL)60Pps-5GEi_FO?zxFIFDBInB$jv#Ia@C>jEkOTq}{oPs9 z<{FpNsbpL&WDHk4o=D5NTs)pbz)F0_c|A7Vq-VQ2tg?m9bgi`A@zv6eu0HRg=Yh#( zyb-yY_<6C=>)p-a3dCOq%)5CT(iOnGFWe!&Az-+0k=nkjs_?7yTX&uNEx62*R1J zUiJFvyU0&$T%X%8U|nUFv`siZwB1*uS;!Ew9i~uEPq%r4uRuQw;CY&3U`Vp2DQdDb z9Vk*vN*%nn=yFe3Jowu^8?C=sbWiWc73?%!-xR6GJn<6D%3#GeTC6;(b$InP3bq->>7L_p=kM3RyD6NP+6`J z`|kitv=9xs4TtDSQc?4KJ5~3OF{?h_{|TEwYxxD>HQ*v}2{2o2HrVX#d$5^vD-EPn znIGcc!-BV9I8nYiHsdOH~X-6E7femx|Sb93Myf#75# diff --git a/learning_center/models/course_homework.py b/learning_center/models/course_homework.py index d071fd8..697fb0d 100644 --- a/learning_center/models/course_homework.py +++ b/learning_center/models/course_homework.py @@ -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""" -

课程:{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' - ) + + # 查询当前作业+当前分项已存在明细 + 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""" +

课程:{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' + ) + # ====================== 草稿/关闭作业 ====================== 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 \ 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 37a89a3..a6d2ccb 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='文件名') @@ -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): 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 7c249cc..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 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_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 0000000000000000000000000000000000000000..d6127cd4510d9e3946814c7faca6a34fdb7bef92 GIT binary patch literal 191 zcmX@j%ge<81jc!KS)xGtF^B^LOi;#W0U%>KLkdF*V-77Ii%)HE!_;|g7%3mBdKyyoTQtgU3fQEtWDh4q=Ff%eT L-eV9gVgqsjtxqq& literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..ca83b567ad83b37f3a463351a53c63611d4f9aba GIT binary patch literal 207 zcmX@j%ge<81dh3SS+YR-F^B^LOi;#W0U%>KLkdF*V-72CgqWqjxKTW1v3`NX9$(0PBK}P&?PO*x~Psz_Ww1_EAEhVvzLbCgvrkgXHs5Qge!9;^Q;(GE3s)^$IF~ao7L_OLJ1~ia3B~g6u8^ RF+MOeGBVy{kS$^ZasaMuHlqLl literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..12213e5274ab9ab30b813916926cdaa1d8a5d1dd GIT binary patch literal 7174 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 + + + + + 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 + + + +
+