278 lines
16 KiB
Python
278 lines
16 KiB
Python
# -*- coding: utf-8 -*-
|
||
from pathlib import Path
|
||
|
||
from odoo import api, fields, models
|
||
from odoo.modules.module import get_module_path
|
||
|
||
|
||
class IrModuleModule(models.Model):
|
||
_inherit = 'ir.module.module'
|
||
|
||
module_origin = fields.Selection(
|
||
[('official', '系统原生'), ('local', '本地模块'), ('third_party', '第三方/其它')],
|
||
string='模块来源',
|
||
compute='_compute_module_origin',
|
||
store=True,
|
||
index=True,
|
||
help='Odoo 官方模块标记为「系统原生」;放在 school 目录下的自建模块标记为「本地模块」;其余为「第三方/其它」。',
|
||
)
|
||
|
||
@api.depends('author', 'name')
|
||
def _compute_module_origin(self):
|
||
official_authors = {'Odoo', 'Odoo S.A.', 'Odoo S.A'}
|
||
for rec in self:
|
||
# 优先按物理路径判断: addons/school/ 下的模块视为本地自建
|
||
module_path = get_module_path(rec.name)
|
||
if module_path and Path(module_path).parent.name.lower() == 'school':
|
||
rec.module_origin = 'local'
|
||
continue
|
||
author = (rec.author or '').strip()
|
||
if author in official_authors:
|
||
rec.module_origin = 'official'
|
||
else:
|
||
rec.module_origin = 'third_party'
|
||
|
||
introduction = fields.Html(
|
||
string='模块介绍',
|
||
sanitize=True,
|
||
help='在「模块信息 / 功能与介绍」页展示的模块说明(由 college 维护)',
|
||
)
|
||
features = fields.Html(
|
||
string='功能亮点',
|
||
sanitize=True,
|
||
help='在「模块信息 / 功能与介绍」页展示的功能清单(由 college 维护)',
|
||
)
|
||
knowledge_points = fields.Html(
|
||
string='知识点',
|
||
sanitize=True,
|
||
help='在「模块信息 / 知识点」页展示的核心概念与使用要点(由 college 维护)',
|
||
)
|
||
|
||
@api.model
|
||
def search_panel_select_range(self, field_name, **kwargs):
|
||
"""搜索面板类别列:按 context 开关过滤掉计数为 0 的顶层类别。
|
||
|
||
背景:base 对 ir.module.module 的 search_panel_select_range 做了重写
|
||
(ir_module.py),只要顶层类别的子树里存在任何模块就会返回该类别,
|
||
即使它名下没有一条记录匹配当前搜索域(计数为 0、界面上不显示数字)。
|
||
「实训模块」动作通过 context 开关 shixun_hide_empty_categories 启用本过滤,
|
||
不影响原生「应用」页面;hierarchize/expand 等视图属性在该重写分支中不生效。
|
||
"""
|
||
res = super().search_panel_select_range(field_name, **kwargs)
|
||
if field_name == 'category_id' and self.env.context.get('shixun_hide_empty_categories'):
|
||
res['values'] = [v for v in res.get('values', []) if v.get('__count')]
|
||
return res
|
||
|
||
@api.model
|
||
def populate_module_info(self):
|
||
"""为 school 自建模块写入「功能亮点 / 模块介绍」文案。
|
||
|
||
由 college 的 data 文件通过 <function> 调用,每次升级幂等执行。
|
||
"""
|
||
data = {
|
||
'college': {
|
||
'introduction': (
|
||
"<p>「教育」是面向高校教学的 Odoo 18 综合管理模块,围绕<strong>老师</strong>与"
|
||
"<strong>学生</strong>双角色设计,打通从师生档案、课程建设到作业与学习进展的全流程。</p>"
|
||
"<p>它既是师生管理后台,也是学生端的学习入口,所有数据按角色自动隔离可见范围。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>师生管理</strong>:班级 / 老师 / 学生档案,支持 CSV 模板批量导入,"
|
||
"导入时自动创建登录账号;</li>"
|
||
"<li><strong>课程管理</strong>:老师创建并独立管理课程,支持理论 / 实践两种形式,"
|
||
"含视频与 PPT 课件,以及课程介绍、目标、大纲、特色;</li>"
|
||
"<li><strong>作业习题</strong>:老师布置与批改,学生在线提交;</li>"
|
||
"<li><strong>学习进展</strong>:按角色区分的进展看板,老师看全局、学生看自己;</li>"
|
||
"<li><strong>双角色权限</strong>:老师可管理,学生仅可读与提交。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>角色隔离</strong>:老师与学生看到的数据范围由权限组控制,"
|
||
"同一菜单、同一视图会根据登录角色自动过滤;</li>"
|
||
"<li><strong>CSV 导入规则</strong>:导入学生/老师时会按模板字段自动创建用户并绑定 partner,"
|
||
"确保账号与档案一一对应;</li>"
|
||
"<li><strong>课程归属</strong>:课程由创建老师负责维护,其他老师默认不可编辑;</li>"
|
||
"<li><strong>进展看板</strong>:学生的提交、成绩、学习行为会汇总到进展视图,"
|
||
"老师可查看全班统计。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'edu_base': {
|
||
'introduction': (
|
||
"<p>「教育基础权限」是整套教学体系的权限基座,统一管理跨模块复用的通用角色权限组。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li>预置<strong>学生 / 教师 / 教务管理员</strong>三组权限;</li>"
|
||
"<li>作为基础依赖,供 college、learning_center 等模块直接引用;</li>"
|
||
"<li>不绑定具体业务,只负责角色与授权。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>权限组继承</strong>:edu_base 只定义基础组,业务模块通过 group_id 字段"
|
||
"或菜单 access 引用,实现权限分层;</li>"
|
||
"<li><strong>角色与用户的区别</strong>:组(Group)是权限集合,用户通过分配到组获得权限;</li>"
|
||
"<li><strong>安装顺序</strong>:依赖 edu_base 的模块会自动触发安装,通常无需单独启用。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'learning_center': {
|
||
'introduction': (
|
||
"<p>「学习中心」提供课程的基础信息结构与完整教学链路,是课程内容建设的核心。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>课程 / 章节</strong>:课程下挂载章节,章节可挂资源;</li>"
|
||
"<li><strong>章节资源</strong>:视频、文档等课件管理;</li>"
|
||
"<li><strong>课程作业</strong>:布置作业与学员提交;</li>"
|
||
"<li><strong>成绩管理</strong>:成绩项定义与学员成绩;</li>"
|
||
"<li><strong>课程大纲 / 教学班</strong>:syllabus 与 teaching class 组织。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>课程 ≠ 教学班</strong>:课程是内容模板,教学班是学期/班级维度的开课实例;</li>"
|
||
"<li><strong>章节资源类型</strong>:支持视频、文档等多种资源,资源通过关系模型挂到章节下;</li>"
|
||
"<li><strong>作业与成绩联动</strong>:作业提交状态会作为成绩项的数据来源之一;</li>"
|
||
"<li><strong>大纲复用</strong>:课程大纲(syllabus)可被多个教学班引用,减少重复录入。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'permission_manager': {
|
||
'introduction': (
|
||
"<p>「权限管理」提供可视化的角色与权限配置界面。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>角色(Role)</strong>定义与维护;</li>"
|
||
"<li>基于角色的权限分配视图;</li>"
|
||
"<li>与 edu_base 权限基座配合,细化授权。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>Role 模型</strong>:本模块新增独立的 Role 业务对象,可与 Odoo 原生组做映射;</li>"
|
||
"<li><strong>权限分配入口</strong>:通过表单/列表直接为角色勾选模型级 CRUD 权限;</li>"
|
||
"<li><strong>与 edu_base 的关系</strong>:edu_base 提供组定义,permission_manager 负责"
|
||
"把这些组分配给具体用户或角色。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'student_organization': {
|
||
'introduction': (
|
||
"<p>「学生组织」是学生档案与年级 / 班级结构的基础模块,依赖 hr,"
|
||
"为教学体系提供组织骨架。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>学生组织</strong>:年级、班级等层级管理;</li>"
|
||
"<li><strong>学生信息</strong>:学生基本档案;</li>"
|
||
"<li><strong>基础年级</strong>:年级基础数据初始化。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>与 hr 的关系</strong>:学生档案复用 employee 模型扩展字段,"
|
||
"因此依赖 hr 模块;</li>"
|
||
"<li><strong>年级 → 班级 → 学生</strong>:组织层级自上而下引用,删除上级会受外键约束;</li>"
|
||
"<li><strong>基础数据</strong>:「基础年级」用于初始化常见年级字典,后续可在学生组织中引用。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'yuthon_attendance': {
|
||
'introduction': (
|
||
"<p>「Yuthon 出勤管理」面向教职工的打卡出勤模块,自动计算工时、迟到与出勤状态。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>签到 / 签退</strong>:基于员工记录出勤;</li>"
|
||
"<li><strong>自动计算</strong>:工时、迟到分钟、出勤状态由计算字段得出;</li>"
|
||
"<li><strong>约束校验</strong>:一人一天一条,签到早于签退;</li>"
|
||
"<li><strong>出勤分析</strong>:graph / pivot 统计。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>唯一约束</strong>:同一员工同一天只能有一条出勤记录,避免重复打卡;</li>"
|
||
"<li><strong>状态计算逻辑</strong>:出勤状态由签到/签退时间与阈值比较得出,无需手动选择;</li>"
|
||
"<li><strong>工时公式</strong>:实际工时 = 签退时间 − 签到时间 − 扣除规则;</li>"
|
||
"<li><strong>与薪资联动</strong>:出勤结果会作为 yuthon_wage 计算月度工资的依据。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'yuthon_employee': {
|
||
'introduction': (
|
||
"<p>「Yuthon 员工管理」是大学人事基础,管理教职工主数据,"
|
||
"是整个出勤—薪资体系的数据根。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>教职工档案</strong>:工号 / 部门 / 岗位 / 基本工资;</li>"
|
||
"<li><strong>唯一约束</strong>:工号不可重复;</li>"
|
||
"<li><strong>模块组合</strong>:被出勤、薪资模块通过 _inherit 扩展反向关联。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>工号是核心键</strong>:工号全局唯一,是出勤、薪资多条记录关联员工的基础;</li>"
|
||
"<li><strong>基本工资用途</strong>:基本工资字段写入员工档案,薪资模块每月读取并聚合;</li>"
|
||
"<li><strong>反向扩展模式</strong>:出勤、薪资模块通过 _inherit 员工模型添加字段,"
|
||
"保持主数据单一来源。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'yuthon_login_theme': {
|
||
'introduction': (
|
||
"<p>「校园登录主题」是纯前端外观模块,将 Odoo 登录页美化为教育网站风格,"
|
||
"不新增任何业务数据。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>渐变背景</strong>:教育紫整页渐变;</li>"
|
||
"<li><strong>玻璃质感</strong>:登录卡片 + 品牌标题;</li>"
|
||
"<li><strong>主色统一</strong>:登录按钮与主题色一致。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>纯前端模块</strong>:只包含 SCSS / XML 视图资源,没有模型或业务逻辑;</li>"
|
||
"<li><strong>登录页覆盖</strong>:通过继承 Odoo 登录模板并覆盖样式实现;</li>"
|
||
"<li><strong>主题色变量</strong>:主色在 SCSS 变量中统一定义,修改一处即可全局生效。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
'yuthon_wage': {
|
||
'introduction': (
|
||
"<p>「Yuthon 薪资管理」在月底把员工基本工资与当月出勤结果聚合,"
|
||
"自动计算扣款与实发工资。</p>"
|
||
),
|
||
'features': (
|
||
"<ul>"
|
||
"<li><strong>月度工资条</strong>:基本工资 + 出勤聚合;</li>"
|
||
"<li><strong>自动扣款</strong>:迟到 / 缺勤按规则计算;</li>"
|
||
"<li><strong>批量生成</strong>:向导批量生成工资条;</li>"
|
||
"<li><strong>状态机</strong>:草稿 → 完成。</li>"
|
||
"</ul>"
|
||
),
|
||
'knowledge_points': (
|
||
"<ul>"
|
||
"<li><strong>工资条生成时机</strong>:通常每月月底运行向导,批量生成上月工资条;</li>"
|
||
"<li><strong>聚合逻辑</strong>:系统按员工汇总当月出勤记录的迟到、缺勤数据,"
|
||
"再与基本工资做扣款计算;</li>"
|
||
"<li><strong>状态流转</strong>:草稿状态可重新计算,完成后建议锁定,防止误改;</li>"
|
||
"<li><strong>与出勤模块的关系</strong>:yuthon_wage 依赖 yuthon_attendance,"
|
||
"考勤数据是薪资计算的关键输入。</li>"
|
||
"</ul>"
|
||
),
|
||
},
|
||
}
|
||
for name, vals in data.items():
|
||
rec = self.search([('name', '=', name)], limit=1)
|
||
if rec:
|
||
rec.write(vals)
|
||
# 确保来源字段对所有模块记录完成计算(覆盖历史模块记录)
|
||
self.search([])._compute_module_origin()
|