模块迁移

This commit is contained in:
李鹏宇 2026-07-25 14:23:19 +08:00
parent 782bea26af
commit 1fb7ae189c
63 changed files with 3444 additions and 500 deletions

View File

@ -0,0 +1,3 @@
from . import controllers
from . import models
from . import wizard

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
# email:pengyuthon@163.com
{
'name': 'Yuthon Hr',
'summary': '',
'description': '''
''',
'version': '18.0.2.0.0',
'category': 'Human Resources/Employees',
'license': 'LGPL-3',
'author': 'pengyuthon',
'website': 'https://www.pengyuthon.com',
'depends': ['base', 'hr'],
'data': [
'data/employee_code_data.xml',
'security/ir.model.access.csv',
'views/yuthon_hr_employee_views.xml',
'views/inherit_res_user_views.xml',
'views/inherit_hr_work_location_views.xml',
'views/inherit_res_groups_views.xml',
'wizard/hr_employee_wizard_views.xml',
'wizard/hr_employee_time_wizard_views.xml',
'wizard/reset_password_wizard_viwes.xml',
],
'assets': {
'web.assets_backend': [
'yuthon_hr_employee/static/scss/yuthon_employee_scss.scss',
],
},
'installable': True,
'application': False,
'auto_install': False,
}

View File

@ -0,0 +1 @@
from . import employee_banner_route

View File

@ -0,0 +1,29 @@
from odoo.http import Controller, request, route
class EmployeeBannerRoute(Controller):
@route('/employee/banner/route', type='json', auth='user')
def employee_banner_route(self, **kwargs):
employees = request.env['hr.employee'].sudo()
hired_count = employees.search_count([('use_user_type', '=', 'formal_one')]) # 正式
in_hired_count = employees.search_count([]) # 在职
all_hired_count = employees.search_count([]) # 全职
return {
# 'html': f"""
# <div style="display: flex; justify-content: space-around; align-items: center; text-align: center; padding: 20px; width: 100%;margin-top:25px;margin-bottom:25px;">
# <div style="flex: 1; padding: 20px;">
# <h2>正式</h2>
# <p style="font-size: 24px; color: #4CAF50;">{hired_count}</p>
# </div>
# <div style="flex: 1; padding: 20px;">
# <h2>在职</h2>
# <p style="font-size: 24px; color: #2196F3;">{in_hired_count}</p>
# </div>
# <div style="flex: 1; padding: 20px;">
# <h2>全职</h2>
# <p style="font-size: 24px; color: #FF5722;">{all_hired_count}</p>
# </div>
# </div>
# """
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="employee_code_sequence" model="ir.sequence">
<field name="name">员工编号</field>
<field name="code">employee_code</field>
<field name="prefix">US%(y)s%(month)s%(day)s</field>
<field name="padding">3</field>
</record>
</odoo>

View File

@ -0,0 +1,4 @@
from . import yuthon_hr_employee
from . import hr_employee_page
from . import inherit_res_user
from . import inherit_res_groups

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class YuthonEmergencyContactLine(models.Model):
_name = 'yuthon.emergency.contact.line'
_description = "紧急联系人明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
name = fields.Char(string="姓名")
user_sfz = fields.Char(string="身份证")
user_relationship = fields.Selection([("father", "父亲"),('mother', '母亲'), ("spouse", "妻子"), ('boys', '儿子'), ('daughter', '女儿'), ('didi', '弟弟'),
('husband', '丈夫'), ('sister', '姐姐'), ('brother', '哥哥'), ('sister_do', '妹妹')], string="关系")
workplace = fields.Char(string="工作单位")
post = fields.Char(string="岗位")
phone_number = fields.Char(string="手机号")
home_address = fields.Char(string="家庭住址")
documents_ids = fields.Many2many('ir.attachment', string="附件")
class YuthonWorkExperienceLine(models.Model):
_name = 'yuthon.work.experience.line'
_description = "工作经验明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
start_date = fields.Date(string="开始日期")
end_date = fields.Date(string="结束日期")
work_place = fields.Char(string="工作单位")
post = fields.Char(string="岗位")
contact_user = fields.Char(string="联系人")
phone_number = fields.Char(string="联系人手机号")
reason_leaving = fields.Char(string="离职原因")
class YuthonEducationExperienceLine(models.Model):
_name = 'yuthon.education.experience.line'
_description = "教育经历明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
name_school = fields.Char(string="学校名称")
start_date = fields.Date(string="入学时间")
end_date = fields.Date(string="毕业时间")
in_school_duties = fields.Char(string="在校职务")
professional = fields.Char(string="专业")
learning_style = fields.Selection([
('full_time', '全日制'),
('upgrade', '专升本'),
('adult_education', '成人教育'),
('self_study', '自考'),
('online_education', '网络教育'),
('correspondence', '函授'),
], string="学习方式")
part_time_degree = fields.Char(string="学历")
full_time_degree = fields.Char(string="学位")
documents_ids = fields.Many2many('ir.attachment', string="附件")
class YuthonSkillsCertificatesLine(models.Model):
_name = 'yuthon.skills.certificates.line'
_description = "技能证书管理明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
rank_name = fields.Char(string='职级名称')
rank_type = fields.Char(string='职级类型')
rank_time = fields.Date(string='职级时间')
documents_ids = fields.Many2many('ir.attachment', string="附件")

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class HrJob(models.Model):
_inherit = "hr.job"
_order = 'number asc'
number = fields.Char(string="序号")
job_type_id = fields.Many2one('hr.job.type', string="岗位类别")
start_date = fields.Date(string="生效日期", default=lambda self: fields.Date.today())
end_date = fields.Date(string="失效日期", default=fields.Date.from_string('2099-01-01'))
is_enable = fields.Boolean(string="是否启用", default=False, store=True)
compiling_external = fields.Integer(string="编制外人数")
compiling_status = fields.Selection([('1', '缺编'), ('2', '满编')], string="状态")
missing_number = fields.Integer(string="缺编人数")
establishment_quotas = fields.Integer(string="编制定员")
job_personnel_ids = fields.Many2many('hr.employee', string='在岗人员')
recruitment_employees = fields.Integer(string="招聘人数")
email = fields.Char(string="邮箱")
job_personnel = fields.Text(string="在岗人员")
remarks = fields.Text(string="备注")
is_recruitment_status = fields.Boolean(string="招聘状态")
company_id = fields.Many2one('res.company', string='公司')
is_published = fields.Boolean(string="已发布")
website = fields.Char(string="网站")
skill_ids = fields.Many2many('hr.skill', string="技能")
date_from = fields.Date(string="开始日期")
#任职要求
degree = fields.Selection([('high', '高中'), ('junior_college', '大专'), ('bachelor', '本科'),
('master', '硕士'), ('doctor', '博士')], string="学历")
sex = fields.Selection([('boy', ''), ('girl', ''), ('other', '不限')], string="性别")
age_start = fields.Integer(string="年龄")
age_end = fields.Integer(string="年龄")
political_aspects = fields.Selection([('cpc_dang', '中共党员'), ('nld', '民盟'),
('league_member', '共青团员'), ('people', '群众')], string="政治面貌")
professional = fields.Char(string="专业")
job_title = fields.Char(string="职称")
admissions = fields.Char(string="执业资格")
years_of_service = fields.Integer(string="工作年限")
competency_requirements = fields.Text(string="岗位职责")
other_notes = fields.Text(string="任职要求")
function = fields.Text(string="岗位职责")
department_ids = fields.Many2one('hr.department', string="所属部门")
recruitment_target1 = fields.Selection([('new', '新员工'), ('transfer', '内部调动')], string="招聘目标")
address_id = fields.Many2one('res.partner', string="工作地点")
industry_id = fields.Many2one('res.partner', string="行业")
recruit_employee_id = fields.Many2one('hr.employee', string="招聘负责人")
interviewer_employee_id = fields.Many2one('hr.employee', string="面试官")

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
import datetime
class ResGroups(models.Model):
_inherit = "res.groups"
users_ids = fields.Many2many('res.users', string="权限用户")
employee_ids = fields.Many2many('hr.employee', string="权限员工")
department_ids = fields.Many2many("hr.department", string="权限部门")
def users_unlink(self):
users_to_unlink = self.users.filtered(lambda user: user.name != 'Administrator')
users_to_unlink2 = self.users.filtered(lambda user: user.name != '叶华峰')
self.write({'users': [(3, user.id) for user in users_to_unlink]})
self.write({'users': [(3, user.id) for user in users_to_unlink2]})
def domain_unlink(self):
self.write({'users_ids': [(5, 0, 0)]})
self.write({'employee_ids': [(5, 0, 0)]})
self.write({'department_ids': [(5, 0, 0)]})
def rule_unlink(self):
self.rule_groups.unlink()
self.model_access.unlink()
@api.onchange('employee_ids', 'department_ids')
def _onchange_update_users(self):
"""动态更新 users 字段,基于员工、部门的选择"""
all_user_ids = set()
# 1. 添加选中员工对应的用户
for employee in self.employee_ids:
if employee.user_id:
all_user_ids.add(employee.user_id.id)
# 2. 添加选中部门及其子部门的所有员工对应的用户
def get_department_employees(department):
valid_user_ids = set()
if not department:
return valid_user_ids
# 处理虚拟记录onchange时可能出现
dept_id = department.id.origin if hasattr(department.id, 'origin') else department.id
dept_id = int(dept_id) if isinstance(dept_id, str) else dept_id
# 当前部门员工
employees = self.env['hr.employee'].search([('department_id', '=', dept_id)])
valid_user_ids.update([emp.user_id.id for emp in employees if emp.user_id])
# 递归子部门
child_depts = self.env['hr.department'].search([('parent_id', '=', dept_id)])
for child in child_depts:
valid_user_ids.update(get_department_employees(child))
return valid_user_ids
for department in self.department_ids:
all_user_ids.update(get_department_employees(department))
# 最终更新用户
self.users = [(6, 0, list(all_user_ids))]

View File

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
from odoo.exceptions import UserError
import pypinyin
class ResUsers(models.Model):
_inherit = "res.users"
# access_token2 = fields.Char(string='OAuth 访问令牌2', help='验证消息通知。')
company_ids1 = fields.Many2many('res.company', string='公司', compute='_compute_company_ids1', store=True)
@api.depends("company_ids")
def _compute_company_ids1(self):
for record in self:
employee_id = record.env['hr.employee'].search([('user_id', '=', record.id)], limit=1)
is_admin = record.has_group('base.group_system')
if is_admin:
record.company_ids1 = employee_id.company_id
else:
record.company_ids1 = record.company_ids
def update_company_ids1(self):
cron_ids = self.env["res.users"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for cron in cron_ids:
cron._compute_company_ids1()
@api.model
def create(self, vals):
user = super(ResUsers, self).create(vals)
user._compute_company_ids1()
return user
def all_create_users(self):
employee_ids = self.browse(self._context.get('active_ids', self._context.get('active_id')))
for employee in employee_ids:
employee_id = self.env['hr.employee'].search([('name', '=', employee.name)])
if not employee_id:
employee_data = {
'name': employee.name,
'company_id': employee.env.company.id,
'user_id': employee.id,
}
self.env['hr.employee'].create(employee_data)
else:
raise UserError('该用户:%s已经创建员工,请不要重复操作' % employee_id.name)
def all_users_email(self):
employee_ids = self.browse(self._context.get('active_ids', self._context.get('active_id')))
for employee in employee_ids:
if not employee.partner_id.email:
pinyin_list = pypinyin.lazy_pinyin(employee.name, style=pypinyin.NORMAL)
name_pinyin = ''.join(pinyin_list).lower()
email = f"{name_pinyin}@thtjzt.com"
employee.partner_id.write({'email': email})

View File

@ -0,0 +1,373 @@
# -*- coding: utf-8 -*-
from dateutil.relativedelta import relativedelta
from pypinyin import pinyin, lazy_pinyin, STYLE_FIRST_LETTER
from odoo.osv import expression
from odoo import api, fields, models
from datetime import date, datetime
from odoo.exceptions import UserError, ValidationError
nation_list = [('a', '汉族'), ('b', '壮族'), ('v', '满族'), ('d', '回族'), ('e', '苗族'), ('f', '维吾尔族'),
('g', '土家族'), ('h', '彝族'), ('i', '蒙古族'), ('j', '藏族'), ('k', '布依族'), ('l', '侗族'),
('m', '瑶族'), ('n', '朝鲜族'), ('o', '白族'), ('p', '哈尼族'), ('q', '哈萨克族'), ('r', '黎族'),
('s', '傣族'), ('t', '畲族'), ('u', '傈僳族'), ('v', '仡佬族'), ('w', '东乡族'), ('x', '拉祜族'),
('y', '水族'), ('z', '佤族'), ('a1', '纳西族'), ('b1', '羌族'), ('c1', '仫佬族'), ('d1', '锡伯族'),
('e1', '柯尔克孜族'), ('f1', '达斡尔族'), ('g1', '景颇族'), ('h1', '毛南族'), ('i1', '撒拉族'),
('j1', '布朗族'), ('k1', '塔吉克族'), ('l1', '阿昌族'), ('m1', '普米族'), ('n1', '鄂温克族'), ('o1', '怒族'),
('p1', '京族'), ('q1', '基诺族'), ('r1', '德昂族'), ('s1', '保安族'), ('t1', '俄罗斯族'), ('u1', '裕固族'),
('v1', '乌孜别克族'), ('w1', '门巴族'), ('x1', '鄂伦春族'), ('y1', '独龙族'), ('z1', '塔塔尔族'), ('a2', '赫哲族'),
('b2', '高山族'), ('c2', '珞巴族'), ('d2', '壮族')]
class YuthonHrEmployee(models.Model):
"""
人员管理系统 - 教学版
hr.employee 模型的扩展添加个人信息工作信息教育经历等字段
"""
_inherit = "hr.employee"
_order = 'number asc'
# 基本信息
compress_image = fields.Binary(string="压缩头像", attachment=True)
company_ids = fields.Many2many('res.company', related="user_id.company_ids")
number = fields.Char(string="序号", store=True)
is_soldiers = fields.Boolean(string="是否为退伍军人")
user_number = fields.Char(string="员工编号", default=lambda self: self.env['ir.sequence'].next_by_code('employee_code'))
use_user_type = fields.Selection([('formal_one', '正式合同(一签)'), ('labor', '劳务协议'), ('formal_two', '正式合同(二签)'),
('formal_none', '正式合同(无固定)'),
('part_time', '兼职协议'), ('employment', '用工协议'), ('internship', '实习协议')], string="用工形式")
date_end = fields.Date(string="合同结束日期")
end_comparison = fields.Selection([('red', '小于30天'), ('yellow', '小于60天'), ('blue', '小于90天')], string="结束时间对比(颜色)",
compute='_compute_end_comparison', store=True)
# 个人信息
user_sex = fields.Selection([('girl', ''), ('boy', '')], string="性别")
user_state = fields.Selection([('in', '在职'), ('leave', '离职'), ('withdraw', '退休'), ('borrow', '借调'),
('expatriate', '外派'), ('adjunct', '在职(兼职)')],
string="员工状态", default='in')
user_number_start_time = fields.Date(string="身份证开始时间")
department_ids = fields.Many2many('hr.department', string="辅助部门")
user_number_end_time = fields.Date(string="身份证到期时间")
is_identification = fields.Boolean(string="是否填写身份证")
card_documents_id = fields.Many2one('ir.attachment', string="身份证正面")
card_back_documents_id = fields.Many2one('ir.attachment', string="身份证反面")
hukou_documents_id = fields.Many2one('ir.attachment', string="户口本")
marriage_documents_id = fields.Many2one('ir.attachment', string="结婚证")
nation = fields.Selection(nation_list, string="民族")
old = fields.Integer(string="年龄", compute='_compute_info', store=True)
birthday = fields.Date(string="出生年月", compute='_compute_info', tracking=False)
birthday_month = fields.Integer(string="生日月份", compute='_compute_info')
user_type = fields.Char(string="人员类别")
retire_date = fields.Date(string="退休日期")
retire_years = fields.Integer(string="离退休年限", compute='_compute_retire_years')
political_aspects = fields.Selection([('cpc_dang', '中共党员'), ('nld', '民盟'),
('league_member', '共青团员'), ('people', '群众')], string="政治面貌")
dang_data = fields.Date(string="入团(党)时间")
user_origin = fields.Char(string="籍贯")
user_location = fields.Char(string="户口所在地")
is_location = fields.Boolean(string="是否本地")
bank_account = fields.Char(string="银行卡号")
user_location_type = fields.Selection([('formal', '本地农业'), ('labor', '本地非农业'),
('part_time', '外地农业'), ('practice', '外地非农业')], string="户籍类型")
is_local_location = fields.Char(
string="是否外地",
compute='_compute_is_local_location',
)
@api.depends('user_location_type')
def _compute_is_local_location(self):
for rec in self:
if rec.user_location_type in ('formal', 'labor'):
rec.is_local_location = ''
else:
rec.is_local_location = ''
# 工作信息
entry_date = fields.Date(string="入职日期")
confirmation_date = fields.Date(string="转正日期")
company_old = fields.Char(string="司龄", compute='_compute_company_old')
company_old_year = fields.Integer(string="司龄(年)", compute='_compute_company_old')
regularization_year = fields.Integer(string="转正(年)", compute='_compute_regularization_year')
open_start_time = fields.Date(string="参加工作时间")
is_membership = fields.Boolean(string="工会会员")
cumulative_work_month = fields.Integer(string="累计工作月数", compute='_compute_social_insurance_info')
cumulative_work_years = fields.Char(string="累计工作年限", compute='_compute_cumulative_work_years')
interrupt_work_month = fields.Integer(string="中断工作月数")
compute_char = fields.Char(string="工龄", compute='_compute_social_insurance_info')
compute_month = fields.Integer(string="工龄(月)")
social_insurance_date = fields.Date(string="缴纳社保日期")
social_insurance_time = fields.Char(string="社保时长", compute='_compute_social_insurance_info', store=True)
# 教育信息
high_degree = fields.Char(string="最高学历")
high_degree2 = fields.Char(string="最高学位")
graduate_school = fields.Char(string="毕业院校")
profession = fields.Char(string="专业")
graduate_date = fields.Date(string="毕业时间")
job_lv_date = fields.Char(string="职级等级/签发日期(人事员)")
job_lv = fields.Char(string="专业技术/技能等级(劳动局)")
job_qualification_date = fields.Date(string="职业资格/考取日期")
job_lv_change = fields.Char(string="职级变动")
move_note_date = fields.Text(string="异动情况/移动日期")
# 紧急联系人
emergency_contacts_name = fields.Char(string="称谓")
# 其他信息
archiving_agency = fields.Char(string="存档机构")
over_data_time = fields.Datetime(string="最后更新数据时间")
# 紧急联系人
emergency_contact_ids = fields.One2many('yuthon.emergency.contact.line', 'employee_id', string="紧急联系人明细行")
# 工作经历
work_experience_ids = fields.One2many('yuthon.work.experience.line', 'employee_id', string="工作经历明细行")
# 教育经历
education_experience_ids = fields.One2many('yuthon.education.experience.line', 'employee_id', string="教育经历明细行")
# 技能证书管理
skills_certificates_ids = fields.One2many('yuthon.skills.certificates.line', 'employee_id', string="教育经历明细行")
# 拼音搜索
pinyin_name = fields.Char(string="拼音名称", compute='_compute_pinyin', index=True, store=True)
pinyin_min = fields.Char(string='拼音缩写', compute='_compute_pinyin', index=True, store=True)
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company', search='_search_part_of_company')
resource_calendar_id = fields.Many2one(compute='_compute_resource_calendar_id', string="工作时间", store=True)
marital = fields.Selection([
('single', '未婚'),
('married', 'Married'),
('cohabitant', 'Legal Cohabitant'),
('widower', 'Widower'),
('divorced', 'Divorced')
], string='Marital Status', groups="hr.group_hr_user", default='single', tracking=True)
def get_contract_count(self):
return self.env['hr.employee'].search_count([('end_comparison', 'in', ['red', 'yellow', 'blue'])])
def update_end_comparison(self):
employees = self.env['hr.employee'].search([])
for employee in employees:
employee._compute_end_comparison()
@api.depends('date_end')
def _compute_end_comparison(self):
for record in self:
now = date.today()
if record.date_end:
if record.date_end > now:
delta = record.date_end - now
if 60 <= delta.days < 90:
record.end_comparison = 'blue'
elif 30 <= delta.days < 60:
record.end_comparison = 'yellow'
elif delta.days < 30:
record.end_comparison = 'red'
else:
record.end_comparison = None
elif record.date_end < now:
record.end_comparison = 'red'
else:
record.end_comparison = None
else:
record.end_comparison = None
def get_my_employee_action(self):
current_user = self.env.user
my_employee = self.search([('user_id', '=', current_user.id)], limit=1)
return {
'type': 'ir.actions.act_window',
'name': '个人基础信息',
'res_model': 'hr.employee',
'res_id': my_employee.id,
'view_mode': 'form',
'view_type': 'form',
'target': 'current',
'view_id': self.env.ref('yuthon_hr_employee.view_basic_personal_information').id,
}
def change_password_wizard(self):
return {
'name': '修改密码',
'type': 'ir.actions.act_window',
'res_model': 'reset.password.wizard',
'view_mode': 'form',
'target': 'new',
'context': {'default_employee_id': self.env.user.employee_ids.id},
}
@api.depends('confirmation_date')
def _compute_regularization_year(self):
for record in self:
if record.confirmation_date:
record.regularization_year = fields.Date.today().year - record.confirmation_date.year
else:
record.regularization_year = 0
@api.depends('entry_date')
def _compute_company_old(self):
"""司龄计算"""
for record in self:
if record.entry_date:
today = datetime.now().date()
record.company_old_year = today.year - record.entry_date.year + 1
years_diff = today.year - record.entry_date.year
months_diff = today.month - record.entry_date.month
if today.month < record.entry_date.month:
years_diff -= 1
months_diff += 12
record.company_old = str(years_diff) + '' + str(months_diff) + ''
else:
record.company_old = None
record.company_old_year = 0
@api.depends('open_start_time', 'interrupt_work_month')
def _compute_social_insurance_info(self):
for record in self:
cumulative_months = 0
if record.open_start_time:
today = datetime.now().date()
delta = relativedelta(today, record.open_start_time)
cumulative_months = delta.years * 12 + delta.months
record.cumulative_work_month = cumulative_months
total_months = cumulative_months - record.interrupt_work_month
record.compute_month = total_months
if total_months <= 0:
social_insurance_time_str = "0个月"
else:
years = total_months // 12
months = total_months % 12
social_insurance_time_str = f"{years}{months}个月" if years > 0 and months > 0 else (
f"{years}" if years > 0 else f"{months}个月")
record.social_insurance_time = social_insurance_time_str
record.compute_char = social_insurance_time_str
@api.depends('cumulative_work_month')
def _compute_cumulative_work_years(self):
for record in self:
if record.cumulative_work_month:
if record.cumulative_work_month <= 0:
record.social_insurance_time = "0个月"
years = record.cumulative_work_month // 12
months = record.cumulative_work_month % 12
if years > 0:
result_str = f"{years}"
if months > 0:
result_str += f"{months}个月"
else:
result_str = f"{months}个月"
record.cumulative_work_years = result_str
else:
record.cumulative_work_years = None
@api.depends('identification_id')
def _compute_info(self):
for record in self:
if record.identification_id:
self.is_identification = True
id_number = record.identification_id.upper()
if len(id_number) != 18:
record._clear_info()
if not (all(c.isdigit() for c in id_number[:-1]) and (
id_number[-1].isdigit() or id_number[-1] == 'X')):
raise ValueError("身份证号码包含无效字符")
year = int(id_number[6:10])
month = int(id_number[10:12])
day = int(id_number[12:14])
record.birthday = date(year, month, day)
record.birthday_month = month
today = date.today()
if year is not None:
record.old = today.year - year - ((today.month, today.day) < (month, day))
else:
record.old = 0
gender_code = int(id_number[-2])
record.user_sex = 'girl' if gender_code % 2 == 0 else 'boy'
else:
record._clear_info()
self.is_identification = False
def _clear_info(self):
self.birthday = False
self.birthday_month = False
self.old = 0
self.user_sex = False
@api.depends('retire_date')
def _compute_retire_years(self):
"""离退休年限计算"""
for record in self:
if record.retire_date:
today = datetime.now().date()
years_diff = today.year - record.retire_date.year
compute = years_diff
if compute < 0:
record.retire_years = -1 * compute
else:
record.retire_years = 0
else:
record.retire_years = 0
@api.depends_context('uid', 'company')
@api.depends('company_id')
def _compute_is_company(self):
active_company_ids = self.env.companies
for employee in self:
employee.is_company = employee.company_id in active_company_ids
def _search_part_of_company(self, operator, value):
if operator not in ('=', '!=') or not isinstance(value, bool):
raise UserError(('Operation not supported'))
company_ids = self.env.companies.ids
if not value:
operator = '!=' if operator == '=' else '='
if operator == '=':
return [('company_id', 'in', company_ids)]
else:
return [('company_id', 'not in', company_ids)]
@api.onchange('user_location')
def _onchange_user_location(self):
if self.user_location:
self.is_location = True
else:
self.is_location = False
@api.depends('name')
def _compute_pinyin(self):
for employee in self:
employee.pinyin_name = ''.join(lazy_pinyin(employee.name)) if employee.name else ''
employee.pinyin_min = ''.join(lazy_pinyin(employee.name, style=STYLE_FIRST_LETTER)) if employee.name else ''
@api.model
def _name_search(self, name, domain=None, operator='ilike', limit=None, order=None):
domain = domain or []
if name:
has_chinese = any('\u4e00' <= char <= '\u9fff' for char in name)
if has_chinese:
search_domain = [('name', operator, name)]
else:
search_domain = ['|',
('pinyin_name', operator, name),
('name', '=', name)]
domain = expression.AND([search_domain, domain])
return self._search(domain, limit=limit, order=order)
def action_hr_employee(self):
return {
"type": "ir.actions.act_window",
"name": "批量更新员工",
"res_model": "yuthon.hr.employee.wizard",
"view_mode": "form",
"target": "new",
}
def action_hr_time_form(self):
return {
"type": "ir.actions.act_window",
"name": "批量更新时间表",
"res_model": "yuthon.hr.employee.time.wizard",
"view_mode": "form",
"target": "new",
}

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class YuthonWorkExperienceLine(models.Model):
_name = 'yuthon.work.experience.line'
_description = "工作经验明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
start_date = fields.Date(string="开始日期")
end_date = fields.Date(string="结束日期")
work_place = fields.Char(string="工作单位")
post = fields.Char(string="岗位")
contact_user = fields.Char(string="联系人")
phone_number = fields.Char(string="联系人手机号")
reason_leaving = fields.Char(string="离职原因")
class YuthonEducationExperienceLine(models.Model):
_name = 'yuthon.education.experience.line'
_description = "教育经历明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
name_school = fields.Char(string="学校名称")
start_date = fields.Date(string="入学时间")
end_date = fields.Date(string="毕业时间")
in_school_duties = fields.Char(string="在校职务")
professional = fields.Char(string="专业")
learning_style = fields.Selection([('full_time', '全日制'), ('upgrade', '专升本'), ('adult_education', '成人教育'),
('self_study', '自考'), ('online_education', '网络教育'),('correspondence', '函授')], string="学习方式")
part_time_degree = fields.Char(string="学历")
full_time_degree = fields.Char(string="学位")
documents_ids = fields.Many2many('ir.attachment', string="附件")
class YuthonEmergencyContactLine(models.Model):
_name = 'yuthon.emergency.contact.line'
_description = "紧急联系人明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
name = fields.Char(string="姓名")
user_sfz = fields.Char(string="身份证")
user_relationship = fields.Selection([("father", "父亲"), ('mother', '母亲'), ("spouse", "妻子"),
('boys', '儿子'), ('daughter', '女儿'),('didi', '弟弟'),
('husband', '丈夫'), ('sister', '姐姐'), ('brother', '哥哥'),
('sister_do', '妹妹')], string="关系")
workplace = fields.Char(string="工作单位")
post = fields.Char(string="岗位")
phone_number = fields.Char(string="手机号")
home_address = fields.Char(string="家庭住址")
documents_ids = fields.Many2many('ir.attachment', string="附件")
class YuthonSkillsCertificatesLine(models.Model):
_name = 'yuthon.skills.certificates.line'
_description = "技能证书管理明细行"
employee_id = fields.Many2one('hr.employee', string="关联员工")
rank_name = fields.Char(string='职级名称')
rank_type = fields.Char(string='职级类型')
rank_time = fields.Date(string='职级时间')
documents_ids = fields.Many2many('ir.attachment', string="附件")

View File

@ -0,0 +1,8 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_yuthon_hr_employee_wizard,yuthon_hr_employee_wizard,model_yuthon_hr_employee_wizard,base.group_user,1,1,1,1
access_yuthon_emergency_contact_line,yuthon_emergency_contact_line,model_yuthon_emergency_contact_line,base.group_user,1,1,1,1
access_yuthon_work_experience_line,yuthon_work_experience_line,model_yuthon_work_experience_line,base.group_user,1,1,1,1
access_yuthon_education_experience_line,yuthon_education_experience_line,model_yuthon_education_experience_line,base.group_user,1,1,1,1
access_yuthon_skills_certificates_line,yuthon_skills_certificates_line,model_yuthon_skills_certificates_line,base.group_user,1,1,1,1
access_yuthon_hr_employee_time_wizard,yuthon_hr_employee_time_wizard,model_yuthon_hr_employee_time_wizard,base.group_user,1,1,1,1
access_reset_password_wizard,reset_password_wizard,model_reset_password_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_yuthon_hr_employee_wizard yuthon_hr_employee_wizard model_yuthon_hr_employee_wizard base.group_user 1 1 1 1
3 access_yuthon_emergency_contact_line yuthon_emergency_contact_line model_yuthon_emergency_contact_line base.group_user 1 1 1 1
4 access_yuthon_work_experience_line yuthon_work_experience_line model_yuthon_work_experience_line base.group_user 1 1 1 1
5 access_yuthon_education_experience_line yuthon_education_experience_line model_yuthon_education_experience_line base.group_user 1 1 1 1
6 access_yuthon_skills_certificates_line yuthon_skills_certificates_line model_yuthon_skills_certificates_line base.group_user 1 1 1 1
7 access_yuthon_hr_employee_time_wizard yuthon_hr_employee_time_wizard model_yuthon_hr_employee_time_wizard base.group_user 1 1 1 1
8 access_reset_password_wizard reset_password_wizard model_reset_password_wizard base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@ -0,0 +1 @@
<svg width="50" height="50" viewBox="0 0 50 50" xmlns="http://www.w3.org/2000/svg"><path d="M34 17a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" fill="#985184"/><path d="M12 24a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z" fill="#FBB945"/><path d="M46 24a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z" fill="#1AD3BB"/><path d="M25 30H4a4 4 0 0 0-4 4v4a4 4 0 0 0 4 4h21V30Z" fill="#FBB945"/><path d="M46 30H25v12h21a4 4 0 0 0 4-4v-4a4 4 0 0 0-4-4Z" fill="#1AD3BB"/><path d="M12 30h14c6.627 0 12 5.373 12 12H24c-6.627 0-12-5.373-12-12Z" fill="#985184"/></svg>

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

View File

@ -0,0 +1,13 @@
.list_width{
width: 400px !important;
max-width: 400px !important;
}
.list_ids_width{
width: 400px !important;
max-width: 400px !important;
}
.list_employee_width{
width: 80px !important;
max-width: 80px !important;
}

View File

@ -0,0 +1,206 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="hr_job_inherit" model="ir.ui.view">
<field name="name">hr.job.inherit</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_hr_job_tree"/>
<field name="arch" type="xml">
<xpath expr="//list" position="attributes">
<attribute name="limit">20</attribute>
</xpath>
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="string">岗位</attribute>
</xpath>
<xpath expr="//list" position="attributes">
<attribute name="decoration-danger">compiling_status == '1'</attribute>
</xpath>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="string">所属部门</attribute>
</xpath>
<xpath expr="//field[@name='expected_employees']" position="attributes">
<attribute name="column_invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='no_of_employee']" position="attributes">
<attribute name="column_invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='no_of_recruitment']" position="attributes">
<attribute name="column_invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='department_id']" position="after">
<field name="job_personnel_ids" column_invisible="1"/>
<field name="job_personnel" class="list_width"/>
<field name="establishment_quotas"/>
<field name="no_of_employee" string="在编人数"/>
<field name="compiling_external"/>
<field name="missing_number"/>
<field name="recruitment_employees"/>
<field name="compiling_status"/>
<field name="start_date" optional="hide"/>
<field name="end_date" optional="hide"/>
<field name="is_enable" optional="hide"/>
</xpath>
</field>
</record>
<record id="hr_job_inherit_form" model="ir.ui.view">
<field name="name">hr.job.inherit.form</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr.view_hr_job_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='recruitment_page']" position="attributes">
<attribute name="string">基础信息</attribute>
</xpath>
<xpath expr="//page[@name='job_description_page']" position="attributes">
<attribute name="string">任职要求</attribute>
</xpath>
<xpath expr="//page[@name='job_description_page']" position="after">
<attribute name="string">其他信息</attribute>
</xpath>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='department_id']" position="after">
<group string="岗位信息">
<field name="number"/>
<field name="company_id"/>
<field name="department_ids"/>
<field name="job_personnel_ids" widget="many2many_tags"/>
<field name="establishment_quotas"/>
<field name="no_of_employee" string="在编人数"/>
<field name="recruitment_employees"/>
<field name="compiling_external"/>
<field name="missing_number" force_save="1"/>
<field name="compiling_status"/>
<field name="function"/>
</group>
</xpath>
<div name="recruitment_target" position="after">
<group string="招聘信息">
<field name="address_id"/>
<field name="industry_id"/>
<field name="email"/>
<field name="recruit_employee_id"/>
<field name="interviewer_employee_id"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="is_enable"/>
<field name="is_published"/>
</group>
</div>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="string">所属部门</attribute>
<attribute name="required">1</attribute>
</xpath>
<!-- 隐藏活动附件聊天界面 -->
<xpath expr="//chatter" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- 隐藏活动日期 -->
<xpath expr="//field[@name='date_from']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- 隐藏新员工 -->
<xpath expr="//label[@for='no_of_recruitment']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='no_of_recruitment']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='description']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- 隐藏描述 -->
<xpath expr="//div[@name='recruitment_target']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='description']" position="after">
<group col="3">
<group>
<label for="degree"/>
<div>
<field name="degree" class="oe_inline"/>及以上
</div>
<field name="sex"/>
</group>
<group>
<field name="political_aspects"/>
<field name="admissions"/>
</group>
<group>
<field name="professional"/>
<field name="job_title"/>
</group>
</group>
<group>
<group>
<label for="age_start"/>
<div>
<field name="age_start" class="oe_inline"/>
<field name="age_end" class="oe_inline"/>
</div>
</group>
<group>
<label for="years_of_service"/>
<div>
<field name="years_of_service" class="oe_inline"/>年及以上
</div>
</group>
</group>
<group>
<field name="competency_requirements"/>
<field name="other_notes"/>
</group>
</xpath>
<!-- <xpath expr="//field[@name='contract_type_id']" position="attributes">-->
<!-- <attribute name="string">合同类型</attribute>-->
<!-- </xpath>-->
</field>
</record>
<!-- 隐藏skill_ids -->
<record id="custom_hr_job_form_inherit" model="ir.ui.view">
<field name="name">custom.hr.job.form.inherit</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr_recruitment_skills.hr_job_form_inherit_hr_recruitment_skills"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='skill_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
<!-- 隐藏原生的工作地点,另外的电子邮箱等 -->
<record id="hr_job_inherit_hide_user_id" model="ir.ui.view">
<field name="name">hr.job.form1</field>
<field name="model">hr.job</field>
<field name="inherit_id" ref="hr_recruitment.hr_job_survey"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='user_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='interviewer_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='address_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//label[@for='address_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//div[contains(@class, 'o_row') and .//field[@name='address_id']]" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='industry_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//label[@for='alias_name']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//div[@name='alias_def']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="hr_work_location_inherit_form" model="ir.ui.view">
<field name="name">hr.work.location.inherit.form</field>
<field name="model">hr.work.location</field>
<field name="inherit_id" ref="hr.hr_work_location_form_view"/>
<field name="arch" type="xml">
<xpath expr="//form" position="attributes">
<attribute name="create">0</attribute>
</xpath>
<xpath expr="//field[@name='address_id']" position="attributes">
<attribute name="string">联系人</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="res_groups_view_form2" model="ir.ui.view">
<field name="name">unlink.res.groups.form</field>
<field name="model">res.groups</field>
<field name="inherit_id" ref="base.view_groups_form"/>
<field name="arch" type="xml">
<xpath expr="//form" position="attributes">
<attribute name="create">0</attribute>
</xpath>
<xpath expr="//group" position="before">
<header>
<button class="oe_highlight" name="users_unlink" string="清除用户" type="object"/>
<button class="oe_highlight" name="domain_unlink" string="清除过滤" type="object"/>
<button class="oe_highlight" name="rule_unlink" string="清除规则权限" type="object"/>
</header>
</xpath>
<xpath expr="//field[@name='share']" position="after">
<field name="users_ids" widget="many2many_tags" invisible="1"/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="department_ids" widget="many2many_tags" options='{"no_create": True}'/>
</xpath>
<xpath expr="//field[@name='menu_access']" position="attributes">
<attribute name="widget">many2many_checkboxes</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="all_data_create_user" model="ir.actions.server">
<field name="name">批量创建员工</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="binding_model_id" ref="base.model_res_users" />
<field name="state">code</field>
<field name="code">records.all_create_users()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
<record id="all_users_email" model="ir.actions.server">
<field name="name">更新邮件信息</field>
<field name="model_id" ref="base.model_res_users"/>
<field name="binding_model_id" ref="base.model_res_users" />
<field name="state">code</field>
<field name="code">records.all_users_email()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
<record id="inherit_res_users_tree" model="ir.ui.view">
<field name="name">res.users.tree.inherit</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='lang']" position="attributes">
<attribute name="column_invisible">1</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,524 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_basic_personal_information" model="ir.ui.view">
<field name="name">hr.employee.basic.personal.information</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<form string="个人基础信息" create="false" delete="0">
<sheet>
<div class="d-flex justify-content-between align-items-center">
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" readonly="1"/>
</h1>
</div>
<div class="o_employee_avatar m-0 p-0">
<field name="image_1920" widget="image" class="oe_avatar m-0"
options="{&quot;zoom&quot;: true, &quot;preview_image&quot;:&quot;avatar_128&quot;}"/>
</div>
</div>
<group col="2" readonly="1">
<group>
<field name="mobile_phone"/>
<field name="work_phone"/>
<field name="work_email"/>
<field name="user_state" readonly="1" options="{'no_open': True}"/>
<field name="company_id" readonly="1" options="{'no_open': True}"/>
</group>
<group>
<field name="department_id" readonly="1" options="{'no_open': True}"/>
<field name="department_ids" widget="many2many_tags" readonly="1"
options="{'no_open': True}"/>
<field name="number" readonly="1"/>
<field name="user_number" readonly="1" invisible="1"/>
<field name="parent_id" readonly="1" options="{'no_open': True}" string="上级领导"/>
</group>
</group>
<notebook>
<page string="工作经历">
<field name="work_experience_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="work_place"/>
<field name="post"/>
<field name="contact_user"/>
<field name="phone_number"/>
<field name="reason_leaving"/>
</list>
</field>
</page>
<page string="教育经历">
<field name="education_experience_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="learning_style"/>
<field name="name_school"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="part_time_degree"/>
<field name="full_time_degree"/>
<field name="professional"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
<page name="技能证书管理">
<field name="skills_certificates_ids">
<list editable="bottom">
<field name="rank_name"/>
<field name="rank_type"/>
<field name="rank_time"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
<page string="紧急联系人">
<field name="emergency_contact_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="name"/>
<field name="user_relationship"/>
<field name="user_sfz"/>
<field name="workplace"/>
<field name="post"/>
<field name="phone_number"/>
<field name="home_address"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="view_employee_inherit_del_form" model="ir.ui.view">
<field name="name">hr.employee.inherit.del.form.inherit</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<!-- 源码的信息暂时隐藏 -->
<xpath expr="//form" position="attributes">
<attribute name="create">0</attribute>
</xpath>
<xpath expr="//field[@name='parent_id']" position="attributes">
<attribute name="string">上级领导</attribute>
<attribute name="options">"{'no_create': True}"</attribute>
</xpath>
<xpath expr="//field[@name='coach_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='resource_calendar_id']" position="attributes">
<attribute name="readonly">1</attribute>
<attribute name="options">"{'no_open': True}"</attribute>
</xpath>
<xpath expr="//field[@name='tz']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='company_id']" position="attributes">
<attribute name="options">"{'no_open': True}"</attribute>
</xpath>
<xpath expr="//field[@name='work_email']" position="attributes">
<attribute name="options">"{'no_open': True}"</attribute>
</xpath>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="options">"{'no_open': True}"</attribute>
</xpath>
<xpath expr="//field[@name='work_email']" position="after">
<field name="user_state"/>
<field name="company_ids" invisible="1"/>
</xpath>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="widget">ztree_select</attribute>
</xpath>
<xpath expr="//field[@name='bank_account_id']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//page[@name='personal_information']" position="replace">
<page name="personal_information" string="个人隐私信息">
<group>
<group string="基本信息">
<field name="private_street" string="家庭住址"/>
<field name="user_origin"/>
<field name="user_location"/>
<field name="is_location" invisible="1"/>
<field name="hukou_documents_id" invisible="not is_location" widget="preview_many2one"/>
<field name="user_location_type"/>
<field name="private_email" string="邮箱"/>
<field name="private_phone" string="电话"/>
<field name="pinyin_name"/>
<field name="pinyin_min"/>
<!-- 增加两个字段 -->
<field name="is_soldiers"/>
<field name="bank_account_id" context="{'default_partner_id': work_contact_id}"
options="{'no_quick_create': True}" readonly="not id"/>
</group>
<group string="基本信息">
<field name="identification_id"/>
<field name="user_sex"/>
<field name="user_number_start_time"/>
<field name="user_number_end_time"/>
<field name="nation"/>
<field name="birthday"/>
<label for="old"/>
<div>
<field name="old" style="width: 5%"/>
</div>
<label for="birthday_month"/>
<div>
<field name="birthday_month" style="width: 5%"/>
</div>
<field name="political_aspects"/>
<field name="dang_data"/>
<!-- 原字段的关联 暂时保留并隐藏 -->
<field name="country_of_birth" invisible="1"/>
</group>
<group string="教育">
<field name="high_degree"/>
<field name="high_degree2"/>
<field name="job_lv_change"/>
<field name="move_note_date"/>
</group>
<group string="家庭状态">
<field name="marital"/>
<field name="marriage_documents_id" invisible="marital != 'married'"
options="{'no_create': True}"
widget="preview_many2one"/>
<field name="spouse_complete_name" invisible="marital not in ['married', 'cohabitant']"/>
<field name="spouse_birthdate" invisible="marital not in ['married', 'cohabitant']"/>
</group>
</group>
</page>
</xpath>
<xpath expr="//group[@name='identification_group']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='job_id']" position="attributes">
<attribute name="options">"{'no_open': True}"</attribute>
</xpath>
<xpath expr="//field[@name='parent_id']" position="attributes">
<attribute name="options">"{'no_create': True, 'no_open': True}"</attribute>
</xpath>
<xpath expr="//field[@name='job_id']" position="after">
<field name="number"/>
<field name="user_number" invisible="1"/>
</xpath>
<xpath expr="//field[@name='department_id']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='job_id']" position="attributes">
<attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='identification_id']" position="after">
<field name="is_identification" invisible="1"/>
<field name="card_documents_id" widget="preview_many2one" invisible="not is_identification"/>
<field name="card_back_documents_id" widget="preview_many2one" invisible="not is_identification"/>
</xpath>
<xpath expr="//field[@name='category_ids']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='category_ids']" position="after">
<!-- <field name="user_type"/>-->
<field name="retire_date"/>
<!-- <label for="retire_years"/>-->
<!-- <div>-->
<!-- <field name="retire_years" style="width: 5%"/>-->
<!-- </div>-->
<!-- <field name="user_salary"/>-->
</xpath>
<xpath expr="//group[@name='active_group']" position="after">
<group string="工作信息">
<field name="entry_date"/>
<field name="confirmation_date"/>
<field name="company_old"/>
<field name="regularization_year" invisible="1"/>
<field name="open_start_time"/>
<field name="cumulative_work_month"/>
<field name="cumulative_work_years"/>
<field name="interrupt_work_month"/>
<field name="compute_month" invisible="1"/>
<field name="compute_char"/>
<field name="social_insurance_date"/>
<field name="social_insurance_time"/>
<field name="is_membership"/>
</group>
<group string="其他">
<field name="archiving_agency"/>
<field name="over_data_time"/>
</group>
</xpath>
<xpath expr="//page[@name='hr_settings']" position="before">
<page string="工作经历">
<field name="work_experience_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="work_place"/>
<field name="post"/>
<field name="contact_user"/>
<field name="phone_number"/>
<field name="reason_leaving"/>
</list>
</field>
</page>
<page string="教育经历">
<field name="education_experience_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="learning_style"/>
<field name="name_school"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="part_time_degree"/>
<field name="full_time_degree"/>
<field name="professional"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
<page name="技能证书管理">
<field name="skills_certificates_ids">
<list editable="bottom">
<field name="rank_name"/>
<field name="rank_type"/>
<field name="rank_time"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
<page string="紧急联系人">
<field name="emergency_contact_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="name"/>
<field name="user_relationship"/>
<field name="user_sfz"/>
<field name="workplace"/>
<field name="post"/>
<field name="phone_number"/>
<field name="home_address"/>
</list>
</field>
</page>
</xpath>
</field>
</record>
<record id="view_employee_inherit_del_list" model="ir.ui.view">
<field name="name">hr.employee.inherit.del.tree</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_tree"/>
<field name="arch" type="xml">
<xpath expr="//list" position="replace">
<list string="员工列表" limit="15"
decoration-info="end_comparison == 'blue'"
decoration-danger="end_comparison == 'red'"
decoration-warning="end_comparison == 'yellow'"
default_order="number asc">
<header>
</header>
<field name="end_comparison" column_invisible="1"/>
<field name="name"/>
<field name="department_id"/>
<field name="work_phone"/>
<field name="mobile_phone"/>
<field name="work_email"/>
<field name="political_aspects"/>
<field name="dang_data"/>
<field name="date_end"/>
<!-- <field name="version"/>-->
<field name="identification_id"/>
<field name="user_sex"/>
<field name="nation"/>
<field name="birthday_month"/>
<field name="old"/>
<field name="entry_date"/>
<field name="confirmation_date"/>
<field name="interrupt_work_month"/>
<field name="cumulative_work_month"/>
<field name="cumulative_work_years"/>
<field name="graduate_school"/>
<field name="profession"/>
<field name="high_degree"/>
<field name="high_degree2"/>
<field name="graduate_date"/>
<field name="user_type"/>
<field name="number"/>
<field name="company_id"/>
<field name="work_location_id" column_invisible="1"/>
</list>
</xpath>
</field>
</record>
<!--通讯录 -->
<record id="view_employee_contacts_tree" model="ir.ui.view">
<field name="name">hr.employee.inherit.del.form</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<list string="通讯录" create="false" delete="false" editable="bottom">
<field name="name" readonly="1"/>
<field name="retire_date" readonly="1"/>
<field name="work_phone" readonly="1"/>
<field name="mobile_phone" readonly="1"/>
<field name="department_id" readonly="1"/>
</list>
</field>
</record>
<record id="view_employee_contacts_search" model="ir.ui.view">
<field name="name">hr.employee.contacts.search</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<search string="通讯录">
<field name="name" string="姓名" />
</search>
</field>
</record>
<record id="member_view_search" model="ir.ui.view">
<field name="name">hr.employee.search</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<search string="工会会员">
<field name="name"/>
<field name="department_id"/>
</search>
</field>
</record>
<!--会员列表-->
<record id="view_employee_member_tree" model="ir.ui.view">
<field name="name">hr.employee.member</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<list string="会员列表" create="false" delete="false" editable="bottom" default_order="number asc">
<field name="name" readonly="1"/>
<field name="work_phone" readonly="1"/>
<field name="department_id" readonly="1" options='{"no_open": True, "no_create": True}'/>
<field name="user_type" readonly="1"/>
<field name="is_local_location" readonly="1" string="是否外地"/>
<field name="identification_id" readonly="1"/>
<field name="entry_date" readonly="1"/>
<field name="departure_date" readonly="1"/>
<field name="mobile_phone" readonly="1"/>
<field name="marital" readonly="1"/>
<field name="is_company" column_invisible="1"/>
<field name="company_id" column_invisible="1"/>
</list>
</field>
</record>
<!--手机转交:树形列表(桌面端弹窗使用) -->
<record id="view_employee_phone_tree" model="ir.ui.view">
<field name="name">hr.employee.member</field>
<field name="model">hr.employee</field>
<field name="priority" eval="20"/>
<field name="arch" type="xml">
<list string="人员" create="false" delete="false" editable="bottom">
<field name="name" readonly="1"/>
<field name="department_id" readonly="1" options='{"no_open": True, "no_create": True}'/>
</list>
</field>
</record>
<record id="view_employee_inherit_filter" model="ir.ui.view">
<field name="name">hr.employee.inherit.search</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_filter"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='job_id']" position="after">
<field name="pinyin_name"/>
<field name="pinyin_min"/>
</xpath>
<xpath expr="//filter[@name='inactive']" position="after">
<separator/>
<filter string="在职" name="filter_user_state_in" domain="[('user_state', '=', 'in')]"/>
<filter string="离职" name="filter_user_state_leave" domain="[('user_state', '=', 'leave')]"/>
<filter string="退休" name="filter_user_state_withdraw" domain="[('user_state', '=', 'withdraw')]"/>
<filter string="借调" name="filter_user_state_borrow" domain="[('user_state', '=', 'borrow')]"/>
<filter string="外派" name="filter_user_state_expatriate" domain="[('user_state', '=', 'expatriate')]"/>
<filter string="在职(兼职)" name="filter_user_state_adjunct" domain="[('user_state', '=', 'adjunct')]"/>
</xpath>
<xpath expr="//filter[@name='group_manager']" position="after">
<filter name="group_is_membership" string="工会会员" context="{'group_by': 'is_membership'}"/>
<separator/>
<filter string="即将到期" name="end"
domain="[('end_comparison', 'in', ['red', 'yellow', 'blue'])]"/>
</xpath>
</field>
</record>
<record id="all_data_write_user" model="ir.actions.server">
<field name="name">批量更新员工</field>
<field name="model_id" ref="hr.model_hr_employee"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="code">action = model.action_hr_employee()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
<record id="hr.open_view_employee_list_my" model="ir.actions.act_window">
<field name="view_mode">list,kanban,form,activity,graph,pivot</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('hr.view_employee_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('hr.view_employee_form')})]"/>
</record>
<record id="all_employee_contacts_action" model="ir.actions.act_window">
<field name="name">通讯录</field>
<field name="res_model">hr.employee</field>
<field name="domain">[('is_company', '=', True)]</field>
<field name="view_mode">list,kanban,form,search</field>
<field name="search_view_id" ref="view_employee_contacts_search"/>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('yuthon_hr_employee.view_employee_contacts_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('hr.view_employee_form')})]"/>
</record>
<record id="action_server_my_employee" model="ir.actions.server">
<field name="name">个人基础信息</field>
<field name="model_id" ref="hr.model_hr_employee"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="code">
action = model.get_my_employee_action()
</field>
</record>
<record id="action_password_reset_board" model="ir.actions.server">
<field name="name">重置密码</field>
<field name="model_id" ref="hr.model_hr_employee"/>
<field name="binding_model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="code">
action = model.change_password_wizard()
</field>
</record>
<record id="all_employee_member_action" model="ir.actions.act_window">
<field name="name">会员列表</field>
<field name="res_model">hr.employee</field>
<field name="domain">[('is_membership', '=', True), ('is_company', '=', True)]</field>
<field name="view_mode">list,kanban,form,search</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('yuthon_hr_employee.view_employee_member_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('hr.view_employee_form')})]"/>
<field name="search_view_id" ref="member_view_search"/>
</record>
<record id="hr.open_view_employee_list_my" model="ir.actions.act_window">
<field name="view_mode">list,kanban,form,activity,graph,pivot</field>
</record>
<!-- <record id="all_data_write_time_form" model="ir.actions.server">-->
<!-- <field name="name">批量更新时间表</field>-->
<!-- <field name="model_id" ref="hr.model_hr_employee"/>-->
<!-- <field name="binding_model_id" ref="hr.model_hr_employee"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">action = model.action_hr_time_form()</field>-->
<!-- </record>-->
<!-- <record id="view_employee_form_inherit_hr_attendance" model="ir.ui.view">-->
<!-- <field name="name">hr.employee.attendance.form</field>-->
<!-- <field name="model">hr.employee</field>-->
<!-- <field name="inherit_id" ref="hr_attendance.view_employee_form_inherit_hr_attendance"/>-->
<!-- <field name="arch" type="xml">-->
<!-- &lt;!&ndash; 源码的信息暂时隐藏 &ndash;&gt;-->
<!-- <xpath expr="//group[@name='managers']" position="attributes">-->
<!-- <attribute name="invisible">1</attribute>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<menuitem id="menu_personal_information" name="个人信息" parent="hr.menu_hr_root" action="action_server_my_employee" sequence="45"/>
</odoo>

View File

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="yuthon_inherit_hr_employee" model="ir.ui.view">
<field name="name">yuthon.inherit.hr.employee</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<!-- 隐藏个人隐私信息里紧急,教育,家庭状态,工作许可证 -->
<xpath expr="//page[@name='personal_information']" position="replace">
<page name="Personal Information" string="个人基础信息" groups="hr.group_hr_user">
<group>
<group string="基本信息">
<field name="home_address"/>
<field name="user_origin"/>
<field name="user_location"/>
<field name="user_location_type"/>
<!-- 原生邮箱和电话 -->
<field name="private_email" placeholder="e.g. myprivateemail@example.com" string="邮箱"/>
<field name="private_phone" string="电话"/>
<field name="pinyin_name" invisible="1"/>
<field name="pinyin_min" invisible="1"/>
<field name="dang_data"/>
<field name="is_location"/>
<field name="is_soldiers"/>
<field name="job_lv_salary"/>
<field name="bank_account_id" context="{'default_partner_id': work_contact_id}"
options="{'no_quick_create': True}" readonly="not id"/>
</group>
<group string="个人信息">
<field name="identification_id"/>
<field name="card_documents_id" widget="preview_many2one" invisible="not is_identification"/>
<field name="card_back_documents_id" widget="preview_many2one" invisible="not is_identification"/>
<field name="user_sex"/>
<field name="user_number_start_time"/>
<field name="user_number_end_time"/>
<field name="nation"/>
<field name="birthday"/>
<field name="old"/>
<field name="birthday_month"/>
<field name="political_aspects"/>
</group>
<!-- 隐藏个人隐私信息里紧急,教育,家庭状态,工作许可证 -->
<!-- <group string="Emergency" name="emergency">-->
<!-- <field name="emergency_contact"/>-->
<!-- <field name="emergency_phone" class="o_force_ltr"/>-->
<!-- <separator string="Family Status"/>-->
<!-- <field name="marital"/>-->
<!-- <field name="spouse_complete_name" invisible="marital not in ['married', 'cohabitant']"/>-->
<!-- <field name="spouse_birthdate" invisible="marital not in ['married', 'cohabitant']"/>-->
<!-- <field name="children"/>-->
<!-- </group>-->
<!-- <group string="Education">-->
<!-- <field name="certificate"/>-->
<!-- <field name="study_field"/>-->
<!-- <field name="study_school"/>-->
<!-- <separator name="has_work_permit" string="Work Permit"/>-->
<!-- <field name="visa_no"/>-->
<!-- <field name="permit_no"/>-->
<!-- <field name="visa_expire"/>-->
<!-- <field name="work_permit_expiration_date"/>-->
<!-- <field name="work_permit_name" invisible="1"/>-->
<!-- <field name="has_work_permit" widget="work_permit_upload" filename="work_permit_name"/>-->
<!-- </group>-->
</group>
</page>
</xpath>
<xpath expr="//field[@name='parent_id']" position="before">
<field name="user_number"/>
<field name="number"/>
</xpath>
<xpath expr="//page[@name='public']" position="before">
<page string="工作经历">
<field name="work_experience_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="work_place"/>
<field name="post"/>
<field name="contact_user"/>
<field name="phone_number"/>
<field name="reason_leaving"/>
</list>
</field>
</page>
<page string="教育经历">
<field name="education_experience_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="learning_style"/>
<field name="name_school"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="in_school_duties"/>
<field name="professional"/>
<field name="part_time_degree"/>
<field name="full_time_degree"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
<page string="技能证书">
<field name="skills_certificates_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="rank_name"/>
<field name="rank_type"/>
<field name="rank_time"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
<page string="紧急联系人">
<field name="emergency_contact_ids">
<list editable="bottom">
<field name="employee_id" column_invisible="1"/>
<field name="name"/>
<field name="user_relationship"/>
<field name="user_sfz"/>
<field name="workplace"/>
<field name="post"/>
<field name="phone_number"/>
<field name="home_address"/>
<field name="documents_ids" widget="preview_many2many"/>
</list>
</field>
</page>
</xpath>
</field>
</record>
</odoo>

View File

@ -0,0 +1,3 @@
from . import hr_employee_wizard
from . import hr_employee_time_wizard
from . import reset_password_wizard

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, UserError
class YuthonHrEmployeeTimeWizard(models.Model):
_name = 'yuthon.hr.employee.time.wizard'
department_id = fields.Many2one('hr.department', string='部门')
employee_ids = fields.Many2many('hr.employee', compute='_compute_all_employees', string='所有员工' ,store=True, )
attendance_id = fields.Many2one('resource.calendar', string='考勤时间表')
@api.depends('department_id')
def _compute_all_employees(self):
for record in self:
if record.department_id:
record.employee_ids = self.get_all_employees(record.department_id)
else:
record.employee_ids = False
def get_all_employees(self, department):
employees = self.env['hr.employee'].search([('department_id', '=', department.id)])
child_departments = self.env['hr.department'].search([('parent_id', '=', department.id)])
for child_department in child_departments:
employees += self.get_all_employees(child_department)
return employees
def confirm(self):
for employee_id in self.employee_ids:
if self.attendance_id:
employee_id.write({'resource_calendar_id': self.attendance_id.id})
else:
employee_id.write({'resource_calendar_id': False})

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_hr_employee_time_wizard_form" model='ir.ui.view'>
<field name="name">考勤时间表</field>
<field name="model">yuthon.hr.employee.time.wizard</field>
<field name="arch" type="xml">
<form string="考勤时间表">
<sheet>
<group>
<field name="department_id" widget="ztree_select" options='{"no_create": True}'/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="attendance_id"/>
</group>
</sheet>
<footer>
<button name="confirm" string="确认" type="object" class="oe_highlight"/>
<button string="返回" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1,369 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, UserError
from datetime import datetime, timedelta
import xlrd
import base64
import datetime
from datetime import datetime
import pandas as pd
class YuthonHrEmployeeWizard(models.Model):
_name = 'yuthon.hr.employee.wizard'
excel_file = fields.Binary(string="上传Excel表")
excel_name = fields.Char(string="文件名")
def confirm(self):
"""
导入excel文件,按行读取数据并处理
:return: 导入员工信息表并更新
"""
if not self.excel_file:
raise ValidationError('请上传文件。')
book = xlrd.open_workbook(file_contents=base64.decodebytes(self.excel_file))
sh = book.sheet_by_index(0)
for rx in range(1, sh.nrows):
row = sh.row(rx)
self.update_bank_id(row)
return {
'name': '批量更新员工',
'type': 'ir.actions.act_window',
'view_mode': 'list',
'res_model': 'hr.employee',
}
def employee_user_data(self, row):
employee_name = self.env['hr.employee'].search([('name', '=', row[0].value)])
base_date = datetime(1900, 1, 1)
if row[1].value:
entry_date = base_date + timedelta(days=row[1].value - 2) if row[1].value > 0 else base_date
else:
entry_date = None
if row[2].value:
open_start_time = base_date + timedelta(days=row[2].value - 2) if row[2].value > 0 else base_date
else:
open_start_time = None
# if row[6].value:
# dang_data = base_date + timedelta(days=row[6].value - 2) if row[6].value > 0 else base_date
# else:
# dang_data = None
# political_aspects_dict = {'中共党员': 'cpc_dang', '民盟': 'nld', '共青团员': 'league_member', '群众': 'people', '': ''}
# political_aspects = political_aspects_dict[row[5].value]
# card = row[1].value if row[1].value else None
# last_attendance = row[7].value
# if row[2].value:
# date1 = pd.to_datetime(row[2].value, unit='d', origin='1899-12-30')
# else:
# date1 = None
# interrupt_work_month = row[3].value
# number = row[0].value
employee_date = {
'entry_date': entry_date,
'open_start_time': open_start_time,
# 'dang_data':dang_data,
# 'identification_id':card,
# 'political_aspects': political_aspects,
# 'last_attendance': last_attendance,
}
if employee_name:
employee_name.write(employee_date)
# text = row[1].value
# text = text.replace('\\', '')
# if department_ids:
# department_id = department_ids[0]
# else:
# raise UserError(f'{text}不存在,请检查。')
# = self.env['hr.job'].search([('name', '=', row[2].value)])
# use_user_type_dict = {'正式职工': 'formal', '劳务协议': 'labor', '兼职协议': 'part_time', '兼职用工': 'employment', '': ''}
# use_user_type = use_user_type_dict[row[1].value]
# job_lv_id = self.env['yuthon.job.lv'].search([('name', '=', row[9].value)])
# marital_dict = {'单身': 'single', '已婚': 'married', '合法同居者': 'cohabitant', '丧偶': 'widower', '离异': 'divorced', '': ''}
# marital = marital_dict[row[19].value]
# role = row[4].value.split('')
# role_list =[]
# user_role_id = self.env['soong.user.role']
# for i in role:
# role_id = user_role_id.search([('name', '=', i)])
# if not role_id:
# role_id = user_role_id.create({'name': i})
# role_list.append(role_id.id)
# use_user_type_dict = {'正式合同': 'formal_one', '劳务协议': 'labor', '正式合同(二签)': 'formal_two', '正式合同(无固定)': 'formal_none',
# '兼职协议': 'part_time', '用工协议': 'employment', '实习协议': 'internship'}
#
# user_role_id = self.env['soong.user.role'].search([('name', '=', row[3].value)], limit=1)
# job_id = self.env['hr.job'].search([('name', '=', row[4].value)])
# parent_id = self.env['hr.employee'].search([('name', '=', row[6].value)])
# open_start_time = row[17].value
# if open_start_time:
# if type(open_start_time) == float:
# entry_date = datetime.fromtimestamp(open_start_time)
# else:
# entry_date = datetime.strptime(open_start_time, '%Y-%m-%d')
# phone = row[21].value if row[21].value else None
# employee_date = {
# 'identification_id':card,
# 'open_start_time':entry_date,
# 'mobile_phone':phone,
# }
# employee_data = {
# 'use_user_type': use_user_type if use_user_type else None, # 用工形式
# 'marital': marital if marital else None, # 婚姻
# 'user_type': row[23].value if row[23].value else None, # 人员类别
# 'high_degree': row[30].value if row[30].value else None, # 最高学历
# 'seniority_prize_pick_up_status': row[43].value if row[43].value else None, # 工龄相关年功奖领取情况
# 'high_degree2': row[31].value if row[31].value else None, # 最高学位
# 'new_start_contract_data': datetime.strptime(row[28].value, '%Y/%m/%d').date() if row[28].value else None, # 最新合同开始日期
# 'political_aspects': political_aspects if political_aspects else None, # 政治面貌
# 'birthday': datetime.strptime(row[20].value, '%Y/%m/%d').date() if row[20].value else None, # 出生年月
# 'dang_data': datetime.strptime(row[27].value, '%Y/%m/%d').date() if row[27].value else None, # 入党(团)时间
# 'retire_date': datetime.strptime(row[24].value, '%Y/%m/%d').date() if row[24].value else None, # 退休日期
# 'new_end_contract_data': datetime.strptime(row[29].value, '%Y/%m/%d').date() if row[29].value else None, # 最新合同终止日期
# 'job_lv_id': job_lv_id.id, # 最新职级 有几个没有
# 'user_sex': 'boy' if row[18].value == '男' else 'girl', # 性别
# 'nation': row[17].value if row[17].value else None, # 民族
# 'identification_id': row[14].value if row[14].value else None, # 身份证号
# 'entry_date': datetime.strptime(row[11].value, '%Y/%m/%d').date() if row[11].value else None, # 入职时间
# 'confirmation_date': datetime.strptime(row[12].value, '%Y/%m/%d').date() if row[12].value else None, # 转正时间
# 'user_number_start_time': datetime.strptime(row[15].value, '%Y/%m/%d').date() if row[15].value else None, # 身份证有效期开始时间
# 'user_number_end_time': datetime.strptime(row[16].value, '%Y/%m/%d').date() if row[16].value else None, # 身份证有效期结束时间
# 'mobile_phone': int(row[8].value) if row[8].value else None, # 手机号码
# 'work_phone': int(row[7].value) if row[7].value else None, # 办公电话
# 'user_number': row[3].value if row[3].value else None, # 员工编号
# }
# if employee_name:
# employee_name.write(employee_date)
#
# # 处理
# for rx in range(1, sh2.nrows):
# row2 = sh2.row(rx)
# if row2[0].value == row[4].value:
# existing_education = employee_name.education_experience_ids.filtered(lambda e: e.name_school == row2[1].value)
# education_data = {
# 'name_school': row2[1].value if row2[1].value else None, # 学校名称
# 'end_date': datetime.strptime(row2[2].value, '%Y/%m/%d').date() if row2[2].value else None, # 毕业时间
# 'professional': row2[3].value if row2[3].value else None, # 专业
# 'Part_time_degree': row2[4].value if row2[4].value else None, # 非全日制学历学位
# 'full_time_degree': row2[5].value if row2[5].value else None, # 全日制学历学位
# }
# if existing_education:
# existing_education.write(education_data)
# else:
# employee_name.write({
# 'education_experience_ids': [(0, 0, education_data)]
# })
#
# for rx in range(1, sh3.nrows):
# row3 = sh3.row(rx)
# if row3[0].value == row[4].value:
# existing_certificate = employee_name.skills_certificates_ids.filtered(lambda s: s.rank_name == row3[1].value)
# certificate_data = {
# 'rank_name': row3[1].value if row3[1].value else None, # 职级名称
# 'rank_time': datetime.strptime(row3[2].value, '%Y/%m/%d').date() if row3[2].value else None, # 职级时间
# }
# if existing_certificate:
# existing_certificate.write(certificate_data)
# else:
# employee_name.write({
# 'skills_certificates_ids': [(0, 0, certificate_data)]
# })
def employee_date(self, value):
date = pd.to_datetime(value, unit='d', origin='1899-12-30').date()
return date
def update_bank_account(self,row):
contacts_list = []
employee_id = self.env['hr.employee'].search([('name', '=', row[1].value)])
company_id = self.env['res.company'].search([('name', '=', row[0].value)])
if row[4].value:
work_phone = int(row[4].value)
else:
work_phone = None
if row[5].value:
mobile_phone = int(row[5].value)
else:
mobile_phone = None
if row[6].value:
entry_date = self.employee_date(row[6].value)
else:
entry_date = None
if row[7].value:
confirmation_date = self.employee_date(row[7].value)
else:
confirmation_date = None
identification_id = row[9].value
if row[10].value:
user_number_start_time = self.employee_date(row[10].value)
else:
user_number_start_time = None
if row[11].value and row[11].value != "长期":
user_number_end_time = self.employee_date(row[11].value)
else:
user_number_end_time = None
user_type = row[17].value
if row[18].value:
retire_date = self.employee_date(row[18].value)
else:
retire_date = None
political_aspects_dict = {'中共党员': 'cpc_dang', '民盟': 'nld', '共青团员': 'league_member', '群众': 'people',
'': ''}
political_aspects = political_aspects_dict[row[20].value]
if row[21].value and row[21].value != "/":
dang_data = self.employee_date(row[21].value)
else:
dang_data = None
user_origin = row[22].value
user_location = row[23].value
if row[26].value:
open_start_time = self.employee_date(row[26].value)
else:
open_start_time = None
interrupt_work_month = row[27].value
cumulative_work_month = row[28].value
part_time_degree = row[29].value
full_time_degree = row[30].value
name_school = row[31].value
professional = row[32].value
job_lv = row[33].value
private_street = row[35].value
contract_id = self.env['hr.contract'].search([('employee_id', '=', employee_id.id)])
education_experience_list = []
education_experience_list.append([0, 0, {'part_time_degree': part_time_degree,
'full_time_degree': full_time_degree,
'name_school': name_school,
'professional': professional,
}])
if row[24].value:
date_start = self.employee_date(row[24].value)
else:
date_start = None
date_end = row[25].value
if date_end == '无固定期限':
date_end = False
else:
date_end = self.employee_date(date_end)
contract_type_id = self.env['hr.contract.type'].search([('name', '=', row[34].value)])
if contract_id:
contract_id.write({
'date_start': date_start,
'date_end': date_end,
'contract_type_id': contract_type_id.id,
})
name = row[36].value
relationship_dict = {
'父亲': 'father',
'姐妹': 'sisters',
'妻子': 'spouse',
'女儿': 'daughter',
'丈夫': 'husband',
'姐姐': 'sister',
'兄弟': 'brother',
'妹妹': 'sister_do',
'父母': 'parents',
'子女': 'children',
'姐弟': 'siblings',
'母亲': 'mother',
'母女': 'daughter',
}
if row[37].value:
user_relationship = relationship_dict[row[37].value]
else:
user_relationship = False
phone_number = row[38].value
archiving_agency = row[39].value
contacts_list.append([0, 0, {'name': name,
'user_relationship': user_relationship,
'phone_number': phone_number,
}])
write_uid = self.env['res.users'].search([('id', '=', employee_id.user_id.id)])
user_type_dict = {
'正式合同(一签)': 'formal_one',
'劳务协议': 'labor',
'正式合同(二签)': 'formal_two',
'正式合同(无固定)': 'formal_none',
'兼职协议': 'part_time',
'用工协议': 'employment',
'实习协议': 'internship',
}
use_user_type = user_type_dict[row[34].value]
employee_date= {
'use_user_type': use_user_type,
'company_id': company_id.id,
'work_phone': work_phone,
'mobile_phone': mobile_phone,
'entry_date': entry_date,
'dang_data':dang_data,
'confirmation_date': confirmation_date,
'user_number_start_time': user_number_start_time,
'user_number_end_time': user_number_end_time,
'retire_date': retire_date,
'open_start_time': open_start_time,
'identification_id': identification_id,
'user_type': user_type,
'political_aspects': political_aspects,
'user_origin': user_origin,
'user_location': user_location,
'interrupt_work_month': interrupt_work_month,
'private_street': private_street,
'job_lv': job_lv,
'write_uid': write_uid,
'archiving_agency': archiving_agency,
'emergency_contact_ids': contacts_list,
'education_experience_ids': education_experience_list
}
#
if employee_id:
employee_id.write({'education_experience_ids': [(5, 0, 0)]})
employee_id.write({'emergency_contact_ids': [(5, 0, 0)]})
employee_id.write(employee_date)
def update_lc_employee(self, row):
employee_id = self.env['hr.employee'].search([('name', '=', row[0].value)])
structure_type_id = self.env['hr.payroll.structure.type'].search([('name', '=', row[2].value), ('company_id.name', '=', '天河粮储')])
if row[3].value:
entry_date = self.employee_date(row[3].value)
else:
entry_date = None
if row[4].value:
open_start_time = self.employee_date(row[4].value)
else:
open_start_time = None
interrupt_work_month = row[6].value
if employee_id:
employee_date = {
'entry_date': entry_date,
'open_start_time': open_start_time,
'interrupt_work_month': interrupt_work_month,
}
employee_id.write(employee_date)
employee_id.contract_id.structure_type_id = structure_type_id.id,
def update_bank_id(self, row):
employee_id = self.env['hr.employee'].search([('name', '=', row[0].value)])
bank_account_id = self.env['res.partner.bank'].search([('acc_number', '=', row[1].value)], limit=1)
res_id = self.env['res.partner'].search([('name', '=', row[0].value)])
if not bank_account_id:
bank_account_id = self.env['res.partner.bank'].create({
'acc_number': row[1].value,
'partner_id': res_id.id,
})
if employee_id:
employee_date = {
'bank_account_id': bank_account_id.id,
}
employee_id.write(employee_date)

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="user_template_downloads_tian_he_action" model="ir.actions.act_url">
<field name="name">员工模板 下载</field>
<field name="target">self</field>
<field name="url">/yuthon_hr_employee/static/others/user_template.xlsx</field>
</record>
<record id="hr_employee_wizard_form" model="ir.ui.view">
<field name="name">审批意见弹窗</field>
<field name="model">yuthon.hr.employee.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<header>
<button name="%(user_template_downloads_tian_he_action)d" string="模板下载" type="action"
class="btn btn-primary"/>
</header>
<group>
<group>
<field name="excel_file" filename="excel_name"/>
<field name="excel_name" invisible="1"/>
</group>
<group>
</group>
</group>
</sheet>
<footer>
<button name="confirm" string="确认" type="object" class="oe_highlight"/>
<button string="返回" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import ValidationError, UserError
class ResetPasswordWizard(models.TransientModel):
_name = 'reset.password.wizard'
employee_id = fields.Many2one('hr.employee', string="关联员工")
password = fields.Char(string='新密码', required=True)
def reset_password(self):
user = self.employee_id.user_id
user.sudo().password = self.password

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="reset_password_wizard_form" model="ir.ui.view">
<field name="name">reset.password.wizard.form</field>
<field name="model">reset.password.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="employee_id" invisible="1"/>
<field name="password"/>
</group>
<footer>
<button name="reset_password" type="object" string="确定修改" class="oe_highlight"/>
<button special="cancel" string="取消" class="oe_link"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-
from . import models
from . import controllers

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
{
'name': "Yuthon notice",
'name': "yuthon notice",
'summary': "通知公告",
'description': """
@ -10,15 +10,15 @@
'category': 'Human Resources/Attendances',
'version': '0.1',
'license': 'LGPL-3',
'depends': ['base', 'hr', 'mail'],
'depends': ['base', 'hr'],
'data': [
'security/ir.model.access.csv',
'data/notice_code_data.xml',
'data/notice_cron_data.xml',
'data/notice_type_data.xml',
'report/notice_print_report.xml',
'report/notice_print_template.xml',
'views/yuthon_notice_views.xml',
'views/yuthon_notice_type_views.xml',
'views/yuthon_cs_tree_views.xml',
'views/yuthon_confirm_users_views.xml',
],
'assets': {
@ -31,3 +31,4 @@
'installable': True,
'application': True,
}

View File

@ -3,4 +3,5 @@
from . import yuthon_notice
from . import yuthon_notice_type
from . import yuthon_confirm_users
from . import hr_department

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from odoo import api, models
class HrDepartment(models.Model):
_inherit = 'hr.department'
@api.depends('name', 'parent_id.name')
@api.depends_context('show_parent_department')
def _compute_display_name(self):
"""当上下文中 show_parent_department=True 时,显示 '直接上级 / 当前部门',只取一级"""
if self.env.context.get('show_parent_department'):
for record in self:
if record.parent_id:
record.display_name = '%s / %s' % (record.parent_id.name, record.name)
else:
record.display_name = record.name
return
return super()._compute_display_name()

View File

@ -6,33 +6,85 @@ from odoo.exceptions import UserError, ValidationError
class YuthonConfirmUsers(models.Model):
_name = "yuthon.confirm.users"
_description = "公告确认信息"
_order = "employee_number asc"
employee_id = fields.Many2one('hr.employee', string='员工')
notice_id = fields.Many2one('yuthon.notice', string="公告名称")
notice_code = fields.Char(string="公告编号")
notice_name = fields.Char(string="公告名称")
state = fields.Selection([('no', '未确认'), ('yes', '已确认')], string="状态")
state = fields.Selection([('to_sent', '待发送'), ('no', '待确认'), ('yes', '已确认')], string="状态")
create_time = fields.Datetime(string='创建时间', default=fields.Datetime.now)
release_date = fields.Date(string="发布日期")
msg_id = fields.Char(string="Msg_id")
# 从员工带出的字段
company_id = fields.Many2one(related='employee_id.company_id', string='公司', store=True)
department_id = fields.Many2one(related='employee_id.department_id', string='部门', store=True)
employee_number = fields.Char(related='employee_id.number', string='序号', store=True)
def withdrawn(self):
if self.state == 'to_sent':
raise UserError('该通知还未发送,无法撤回')
if self.msg_id:
self.sudo().env["wecom.apps"].back_message(self.msg_id, category='notice')
self.state = 'to_sent'
self.msg_id = '已撤回'
def all_withdrawn(self):
confirm_noice_ids = self.env["yuthon.confirm.users"].browse(self._context.get('active_ids', self._context.get('active_id')))
for record in confirm_noice_ids:
if record.state == 'state':
raise UserError(f'{record.notice_id.name}通知还未发送,无法撤回')
record.withdrawn()
def sync_confirm_users_notices(self):
menu_id = self.env['ir.ui.menu'].search([('name', '=', '通知公告')])
for record in self.search([('state', '=', 'no')]):
partner_ids = record.employee_id.user_id.partner_id.id
record.with_context(lang=record.env.lang)._message_auto_subscribe_notify(partner_ids, 'yuthon_notice.message_yuthon_confirm_users_data')
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=yuthon.notice&view_type=list&menu_id={menu_id.id}'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', user_id=record.employee_id.wecom_userid, title="通知公告",
description="您有待确认的通知公告请尽快确认", url=url, btntxt="详细信息")
url = f'https://phone.thtzjt.com/phone/Notice/{self.notice_id.id}?employee_id={record.employee_id.id}'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', touser=record.employee_id.wecom_userid, title="通知公告",
description=f"您有一条【{record.notice_id.name}】的通知公告请尽快确认", url=url, btntxt="详细信息")
def reminders_notice(self):
for rem in self:
if rem.state == 'yes':
raise UserError('已确认不可催办')
confirm_noice_ids = self.env["yuthon.confirm.users"].browse(self._context.get('active_ids', self._context.get('active_id')))
i = 0
for rec in confirm_noice_ids:
menu_id = self.env['ir.ui.menu'].search([('name', '=', '通知公告')])
url = f'https://oa.thtzjt.com/web#id={self.id}&cids=2&action={menu_id.action.id}&model=yuthon.notice&view_type=list&menu_id={menu_id.id}'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', user_id=rec.employee_id.wecom_userid,
title="通知公告(催办)", description="您有待确认的通知公告请尽快确认",
url = f'https://phone.thtzjt.com/phone/Notice/{self.notice_id.id}?employee_id={rec.employee_id.id}'
result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', touser=rec.employee_id.wecom_userid,
title="通知公告(催办)", description=f"您有一条【{rec.notice_id.name}】的通知公告请尽快确认",
url=url, btntxt="详细信息")
partner_ids = rec.employee_id.user_id.partner_id.id
rec.with_context(lang=rec.env.lang)._message_auto_subscribe_notify(partner_ids,
'yuthon_notice.message_yuthon_confirm_users_data')
if result_data:
res_dict = result_data[1]
rec.write({'msg_id': res_dict.get('msgid')})
if res_dict.get('errmsg') == 'ok':
i = i + 1
url = f'https://phone.thtzjt.com/phone/Notice/{self.notice_id.id}?employee_id=298'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice',
touser='18562027762',
title="通知公告",
description="成功发送消息通知" + str(i),
url=url, btntxt="详细信息")
def reminders_notice_send(self):
confirm_noice_ids = self.env["yuthon.confirm.users"].browse(
self._context.get('active_ids', self._context.get('active_id')))
i = 0
for rec in confirm_noice_ids:
url = f'https://phone.thtzjt.com/phone/Notice/{self.notice_id.id}?employee_id={rec.employee_id.id}'
result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice',
touser=rec.employee_id.wecom_userid,
title="通知公告",
description=f"您有一条【{rec.notice_id.name}】待确认的通知公告请尽快确认",
url=url, btntxt="详细信息")
if result_data:
res_dict = result_data[1]
rec.write({'msg_id': res_dict.get('msgid')})
if res_dict.get('errmsg') == 'ok':
i = i + 1
url = f'https://phone.thtzjt.com/phone/Notice/{self.notice_id.id}?employee_id=298'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice',
touser='18562027762',
title="通知公告",
description="成功发送消息通知" + str(i),
url=url, btntxt="详细信息")

View File

@ -2,6 +2,7 @@
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError, UserError
import logging
from datetime import datetime, timedelta, date
_logger = logging.getLogger(__name__)
@ -22,17 +23,15 @@ class YuthonNotice(models.Model):
create_date1 = fields.Date(string="发布日期", default=fields.Date.today())
start_date = fields.Date(string="生效日期", default=fields.Date.today())
end_date = fields.Date(string="终止日期")
sequence = fields.Integer(string="顺序",compute='_compute_sequence',store=True)
sequence = fields.Integer(string="顺序", default=1, compute='_compute_sequence')
employee_ids = fields.Many2many('hr.employee', string="人员范围")
department_ids = fields.Many2many('hr.department', string='部门范围')
users_role_ids = fields.Many2many('res.groups', string='角色范围')
users_role_group_ids = fields.Many2many('res.groups', string='角色组', relation='yuthon_notice_role_group_rel')
notice_state = fields.Selection([('no', '未发布'), ('yes', '已发布'), ('stop', '已终止')], default='no', string="公告状态")
documents_ids = fields.Many2many('ir.attachment', string="附件")
is_notice_roger = fields.Selection([('yes', '已读'), ('no', '未读')], default='no', string="确认状态")
is_change_notice = fields.Boolean(string="是否发送通知", default=True)
attn_notice = fields.Html(string="经办人意见")
notice_text = fields.Html(string="正文")
notice_text1 = fields.Html(string="正文")
read_number = fields.Integer(string='阅读量', compute="_compute_read_number", store=True)
is_this_company = fields.Boolean(string='本公司', default=True)
approval_user_id = fields.Many2one('res.users', string="审核人")
@ -41,6 +40,7 @@ class YuthonNotice(models.Model):
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', default='normal')
active = fields.Boolean('Active', default=True, tracking=True)
work_end = fields.Boolean(string="结束")
users_ids = fields.Many2many(
'res.users',
@ -55,9 +55,36 @@ class YuthonNotice(models.Model):
column1='notice_id',
column2='user_id',
string="当前经办人",
store=True,
default=lambda self: [(6, 0, [self.env.user.id])]
)
# @api.onchange('users_ids')
# def _onchange_users_ids(self):
# today = date.today()
# self.start_date = today
# self.create_date1 = today
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company',
search='_search_part_of_company')
publish_date = fields.Date(string='发布日期')
@api.depends_context('uid', 'company')
@api.depends('company_id')
def _compute_is_company(self):
active_company_ids = self.env.companies
for employee in self:
employee.is_company = employee.company_id in active_company_ids
def _search_part_of_company(self, operator, value):
company_ids = self.env.companies.ids
if not value:
operator = '!=' if operator == '=' else '='
if operator == '=':
return [('company_id', 'in', company_ids)]
else:
return [('company_id', 'not in', company_ids)]
@api.model_create_multi
def create(self, vals_list):
for i in vals_list:
@ -72,42 +99,104 @@ class YuthonNotice(models.Model):
@api.constrains('employee_ids', 'department_ids', 'users_role_ids', 'users_role_group_ids')
def _constrains_employee_related_fields(self):
"""
约束验证当关联字段变化时更新users_ids
"""
def get_department_employees(department_id):
"""递归获取部门及其子部门的员工对应的user_id"""
valid_user_ids = set()
# 查找当前部门的员工使用id替代id.origin
employee_ids = self.env['hr.employee'].search([('department_id', '=', department_id.id)])
# 收集有效的user_id
valid_user_ids.update([emp.user_id.id for emp in employee_ids if emp.user_id])
# 递归处理子部门
child_ids = self.env['hr.department'].search([('parent_id', '=', department_id.id)])
for child in child_ids:
valid_user_ids.update(get_department_employees(child))
return valid_user_ids
for notice in self:
# 避免重复触发约束更新users_ids时跳过本次约束检查
if notice.env.context.get('skip_constrains'):
continue
# 获取部门关联的用户ID
department_user_ids = set()
for department_id in notice.department_ids:
department_user_ids.update(get_department_employees(department_id))
group_role_ids = notice.users_role_group_ids
group_role_ids = notice.users_role_group_ids.user_role_ids
all_users = set(
notice.employee_ids.mapped('user_id').ids +
group_role_ids.mapped('users').ids +
notice.users_role_ids.mapped('users').ids +
group_role_ids.mapped('personnel_ids.user_id').ids +
notice.users_role_ids.mapped('personnel_ids.user_id').ids +
list(department_user_ids)
)
notice.with_context(skip_constrains=True).users_ids = [(6, 0, list(all_users))]
if notice.users_ids and notice.is_change_notice:
conf_old = self.env['yuthon.confirm.users'].sudo().search([('notice_id', '=', notice.id)])
old_employee_ids = conf_old.mapped('employee_id.id')
new_employee_ids = self.env['hr.employee'].sudo().search([
('user_id', 'in', notice.users_ids.ids)
]).ids
add_employee_ids = list(set(new_employee_ids) - set(old_employee_ids))
del_employee_ids = list(set(old_employee_ids) - set(new_employee_ids))
# 3. 处理删除:
if del_employee_ids:
del_conf = conf_old.filtered(lambda c: c.employee_id.id in del_employee_ids)
del_conf.unlink()
if add_employee_ids:
for emp_id in add_employee_ids:
employee = self.env['hr.employee'].sudo().browse(emp_id)
if not employee:
continue
# 防重复:如果已经存在该员工的确认记录,跳过
exist = self.env['yuthon.confirm.users'].sudo().search([
('notice_id', '=', notice.id),
('employee_id', '=', emp_id),
], limit=1)
if exist:
continue
self.env['yuthon.confirm.users'].sudo().create({
'notice_id': notice.id,
'employee_id': emp_id,
'notice_code': notice.code,
'notice_name': notice.name,
'release_date': notice.create_date1,
'state': 'to_sent',
})
def get_no_read_count(self):
return self.search_count([('is_notice_roger', '=', 'no'), ('notice_state', '=', 'yes')])
# 只统计当前用户发布范围内的未读公告,与列表 domain 保持一致
return self.search_count([
('users_ids', 'in', self.env.uid),
('is_notice_roger', '=', 'no'),
('notice_state', '=', 'yes'),
])
def read(self, fields=None, load='_classic_read'):
# 标记是否真的有未读变已读,避免没变化也触发菜单刷新
changed = False
for con in self:
con_id = self.env['yuthon.confirm.users'].search([('notice_id', '=', con.id), ('employee_id', '=', con.env.user.employee_id.id)])
if con_id.employee_id == con.env.user.employee_id:
con.is_notice_roger = 'yes'
con_id.write({'state': 'yes'})
return super(YuthonNotice, self).read(fields=fields, load=load)
# 只有全局状态仍是未读时才处理
if con.is_notice_roger == 'no':
# 查找当前用户对应的公告确认记录
con_id = self.env['yuthon.confirm.users'].search([
('notice_id', '=', con.id),
('employee_id', '=', con.env.user.employee_id.id)
])
# 确认是当前员工本人,再标记为已读
if con_id.employee_id == con.env.user.employee_id:
con.is_notice_roger = 'yes'
con_id.write({'state': 'yes'})
changed = True
res = super(YuthonNotice, self).read(fields=fields, load=load)
# 如果有记录从 未读 变为 已读,清除菜单缓存并通知前端刷新角标
if changed:
self.env.registry.clear_cache()
self.env['sun.badge.menu.mixin'].sudo().trigger_menu_reload()
return res
@api.depends('read_number')
def _compute_read_number(self):
@ -134,38 +223,79 @@ class YuthonNotice(models.Model):
self.sequence = 0
def notice_publish(self):
if self.is_change_notice:
conf_old = self.env['yuthon.confirm.users'].search([('notice_id', '=', self.id), ('employee_id', 'in', self.users_ids.ids)])
for user_id in self.users_ids:
url = f'http://oa.thtzjt.com/webh5#/notice?id={self.id}'
if conf_old:
for co in conf_old:
co.write({'state': 'no'})
else:
self.env['yuthon.confirm.users'].create({
'notice_id': self.id,
'employee_id': user_id.employee_ids.id,
'notice_code': self.code,
'notice_name': self.name,
'state': 'no',
})
we_id = self.env['hr.employee'].search([('id', '=', user_id.employee_ids.id)]).wecom_userid
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='notice', user_id=we_id, title="通知公告",
description=self.name + '发布人:' + self.users_id.name,
url=url, btntxt="详细信息")
self.write({"notice_state": 'yes'})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': '通知公告已经发布',
'sticky': False,
}
}
else:
raise UserError('该公告不允许通知,请勾选通知')
"""发布通知公告:给 yuthon.confirm.users 中尚未发送的记录发企微消息。
- 每人有且仅发一条msg_id 为空才发已发过的不重发
- 发送成功才写回 msg_id企微返回的msgid并更新状态为"待确认"
- 单条失败不影响其他记录失败记录保持"待发送"状态
"""
self.ensure_one()
if self.notice_state == 'yes':
return
self.notice_state = 'yes'
if not self.is_change_notice:
return
# 重新发布时,已发送过消息的记录重置为"待确认",等待用户重新确认(不重发消息)
self.env['yuthon.confirm.users'].sudo().search([
('notice_id', '=', self.id),
('msg_id', '!=', False),
]).write({'state': 'no'})
# 只发送尚未收到消息的记录msg_id 为空),保证每人有且仅一条
confirm_records = self.env['yuthon.confirm.users'].sudo().search([
('notice_id', '=', self.id),
('msg_id', '=', False),
])
sent_count = 0
fail_count = 0
for rec in confirm_records:
employee = rec.employee_id
if not employee or not employee.wecom_userid:
_logger.warning("公告[%s]确认记录%s跳过员工或企微userid为空", self.name, rec.id)
continue
url = f'https://phone.thtzjt.com/phone/Notice/{self.id}?employee_id={employee.id}'
try:
result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(
category='notice',
touser=employee.wecom_userid,
title="通知公告",
description=f"{self.name} 发布人: {self.users_id.name or ''}",
url=url,
btntxt="详细信息"
)
except Exception:
_logger.exception("公告[%s]给员工%s发送企微消息异常", self.name, employee.name)
fail_count += 1
continue
# 发送成功:返回元组第一项为 True 且携带 msgid才写回 msg_id 并更新状态
if result_data and result_data[0] and isinstance(result_data[1], dict) and result_data[1].get('msgid'):
rec.sudo().write({
'msg_id': result_data[1].get('msgid'),
'state': 'no',
})
sent_count += 1
else:
_logger.warning("公告[%s]给员工%s发送失败,返回:%s", self.name, employee.name, result_data)
fail_count += 1
_logger.info("公告[%s]发布完成,成功发送%s条,失败%s", self.name, sent_count, fail_count)
# 发布完毕弹窗提醒
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '发布完毕',
'message': f'已成功发送 {sent_count} 条消息' + (f'{fail_count} 条失败' if fail_count else ''),
'type': 'success',
'sticky': False,
},
}
def look_up_notice_users(self):
return {
@ -179,28 +309,90 @@ class YuthonNotice(models.Model):
},
}
def write(self, vals):
"""重写write方法检测work_end字段变化触发AI主动感知"""
result = super().write(vals)
# 检测work_end从False变为True
if 'work_end' in vals and vals['work_end']:
for record in self:
if record.work_end: # 确保当前值确实是True
self._trigger_ai_guide(record)
return result
def _trigger_ai_guide(self, record):
"""触发AI主动感知查找匹配的规则推送消息到聊天框"""
# 查找匹配的规则
rules = self.env['ai.rule'].sudo().search([
('rule_type', '=', 'active_guide'),
('guide_model', '=', self._name),
('guide_field', '=', 'work_end'),
('guide_field_value', '=', 'True'),
('active', '=', True),
])
for rule in rules:
# 生成提醒内容(替换变量)
message = rule.guide_message or f'{record.name} 流程已结束'
message = message.replace('{record_name}', record.name or '')
message = message.replace('{user_name}', self.env.user.name or '')
# 自动组装按钮动作(后台自动注入 record_id
action_config = {}
if rule.guide_method_name:
action_config = {
'type': 'execute_tool',
'tool': rule.guide_method_name,
'params': {
'record_id': record.id,
'model': self._name,
}
}
# 查找或创建AI对话用当前用户的默认对话
conversation = self.env['ai.conversation'].sudo().search([
('create_uid', '=', self.env.user.id),
], limit=1, order='write_date desc')
if not conversation:
conversation = self.env['ai.conversation'].sudo().create({
'name': 'AI 对话',
})
# 创建AI消息带action
self.env['ai.message'].sudo().create({
'conversation_id': conversation.id,
'role': 'assistant',
'content': message,
'action': json.dumps({
'type': 'guide_button',
'label': rule.guide_button_label or '确认执行',
'action': action_config,
}, ensure_ascii=False) if action_config else None,
})
# TODO: 通过bus.bus推送到前端让聊天框自动显示新消息
# self.env['bus.bus'].sudo()._sendone(self.env.user.partner_id, 'ai_guide_notification', {
# 'conversation_id': conversation.id,
# 'message': message,
# })
def stop_notice(self):
self.write({
'notice_state': 'stop',
'end_date': fields.Date.today(),
})
def action_print_notice(self):
"""打印通知公告"""
self.ensure_one()
report = self.env.ref('yuthon_notice.action_notice_print_report')
return report.report_action(self)
class YuthonCsTree(models.Model):
_name = 'yuthon.cs.tree'
_description = 'Yuthon Customer Service Tree'
name = fields.Char(string='Name', required=True)
def _compute_sequence(self):
for record in self:
record.sequence = 1
# 工作流的指定申请人逻辑?
# 转交的时候选择条件,进行优化
# 报表合同和产品业务
# 企业微信等三方平台框架--和工作流

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="paperformat_notice_small" model="report.paperformat">
<field name="name">Notice Small Top Margin</field>
<field name="format">A4</field>
<field name="margin_top">5</field>
<field name="orientation">Portrait</field>
</record>
<record id="action_notice_print_report" model="ir.actions.report">
<field name="name">通知公告</field>
<field name="paperformat_id" ref="yuthon_notice.paperformat_notice_small"/>
<field name="model">yuthon.notice</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">yuthon_notice.notice_print_template</field>
<field name="report_file">yuthon_notice.notice_print_template</field>
<field name="binding_model_id" ref="model_yuthon_notice"/>
<field name="binding_type">report</field>
<field name="print_report_name">'通知公告_%s_%s' % (object.name or '', object.code or '')</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="notice_print_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<style type="text/css">
/* 表格纵向版式自适应最大800px 居中 */
table { border-collapse: collapse; width: 100%; max-width: 800px; margin: 0 auto; table-layout: fixed; }
td { border: 1px solid #000; padding: 6px; text-align: center; vertical-align: middle; }
.title {
text-align: center;
font-weight: bold;
font-size: 18px;
padding: 8px 0;
}
.header-cell { font-weight: bold; background-color: #d4e6f1;}
/* 附件清单左对齐 */
.attachment-list { text-align: left; }
/* 隐藏意见和审批人之间的竖边框 */
.no-right-border { border-right: none !important; }
.no-left-border { border-left: none !important; }
</style>
<!-- 流水号:页面左上角,位于标题之上,浅灰色 -->
<div style="margin-bottom:8px; text-align:left; color:#999999;">
流水号:<span t-esc="doc.code or ''"/>
</div>
<!-- 通知公告标题 -->
<div class="title">
通知公告
</div>
<table>
<colgroup>
<col style="width:15%"/>
<col style="width:35%"/>
<col style="width:15%"/>
<col style="width:35%"/>
</colgroup>
<tr>
<td class="header-cell"><strong>标题</strong></td>
<td colspan="3"><span t-esc="doc.name or ''"/></td>
</tr>
<tr>
<td class="header-cell"><strong>类型</strong></td>
<td><span t-esc="doc.notice_type_id.name or ''"/></td>
<td class="header-cell"><strong>紧急程度</strong></td>
<td>
<span t-if="doc.urgency_level == 'normal'">普件</span>
<span t-if="doc.urgency_level == 'urgent'">急件</span>
<span t-if="doc.urgency_level == 'emergency'">特急件</span>
</td>
</tr>
<tr>
<td class="header-cell"><strong>发布人</strong></td>
<td><span t-esc="doc.users_id.name or ''"/></td>
<td class="header-cell"><strong>发布部门</strong></td>
<td><span t-esc="doc.department_id.name or ''"/></td>
</tr>
<tr>
<td class="header-cell"><strong>发布日期</strong></td>
<td><span t-esc="doc.create_date1 and doc.create_date1.strftime('%Y-%m-%d') or ''"/></td>
<td class="header-cell"><strong>生效日期</strong></td>
<td><span t-esc="doc.start_date and doc.start_date.strftime('%Y-%m-%d') or ''"/></td>
</tr>
<tr>
<td class="header-cell"><strong>正文</strong></td>
<td colspan="3" class="attachment-list"><span t-field="doc.notice_text1"/></td>
</tr>
<tr>
<td class="header-cell"><strong>附件</strong></td>
<td colspan="3" class="attachment-list">
<t t-foreach="doc.documents_ids" t-as="att">
<div><span t-esc="att.name or ''"/></div>
</t>
</td>
</tr>
<!-- 发布范围 -->
<tr>
<td colspan="4" style="background-color: #d4e6f1; text-align: center;"><strong>发布范围</strong></td>
</tr>
<tr>
<td class="header-cell"><strong>部门范围</strong></td>
<td colspan="3" class="attachment-list">
<t t-foreach="doc.department_ids" t-as="dept">
<span t-esc="dept.name"/>
<t t-if="not dept_last"></t>
</t>
</td>
</tr>
<tr>
<td class="header-cell"><strong>角色组</strong></td>
<td colspan="3" class="attachment-list">
<t t-foreach="doc.users_role_group_ids" t-as="role_group">
<span t-esc="role_group.name"/>
<t t-if="not role_group_last"></t>
</t>
</td>
</tr>
<tr>
<td class="header-cell"><strong>角色范围</strong></td>
<td colspan="3" class="attachment-list">
<t t-foreach="doc.users_role_ids" t-as="role">
<span t-esc="role.name"/>
<t t-if="not role_last"></t>
</t>
</td>
</tr>
<tr>
<td class="header-cell"><strong>人员范围</strong></td>
<td colspan="3" class="attachment-list">
<t t-foreach="doc.employee_ids" t-as="emp">
<span t-esc="emp.name"/>
<t t-if="not emp_last"></t>
</t>
</td>
</tr>
<!-- 审批详情 -->
<tr>
<td colspan="4" style="background-color: #d4e6f1; text-align: center;"><strong>审批详情</strong></td>
</tr>
<t t-set="logs_ordered" t-value="doc.log_ids.sorted(lambda x: x.date_create or '')"/>
<t t-foreach="logs_ordered" t-as="log">
<tr>
<td colspan="1"><span t-esc="log.name or '节点'"/></td>
<td class="no-right-border" colspan="2"><span t-esc="log.comment or ''"/></td>
<td class="no-left-border" colspan="1">
<t t-if="log.done_by">
<span t-esc="log.done_by.name"/> <span t-esc="log.lase_click_date and log.lase_click_date.strftime('%Y-%m-%d') or ''"/>
</t>
</td>
</tr>
</t>
</table>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -3,3 +3,7 @@
width: 80px !important;
max-width: 80px !important;
}
.btn-blue {
color: #0069ff !important;
}

View File

@ -1,18 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_confirm_users_tree" model="ir.ui.view">
<field name="name">yuthon.confirm.users.tree</field>
<record id="view_yuthon_confirm_users_list" model="ir.ui.view">
<field name="name">yuthon.confirm.users.list</field>
<field name="model">yuthon.confirm.users</field>
<field name="arch" type="xml">
<list create="false" edit="false"
string="公告确认单" js_class="custom_notice_tree_button" editable="bottom">
<list string="公告确认信息" js_class="custom_notice_tree_button" editable="bottom">
<header>
<button class="oe_stat_button" name="reminders_notice" string="批量催办" type="object" icon="fa-bars"/>
<button name="reminders_notice" string="批量催办" type="object" class="oe_stat_button" icon="fa-bars"/>
<button name="all_withdrawn" string="批量撤回" type="object" icon="fa-bars" class="oe_stat_button" confirm="确认要撤回吗?"/>
<button name="reminders_notice_send" string="批量发送" type="object" icon="fa-bars" class="oe_stat_button"/>
</header>
<field name="employee_id" widget="ztree_select"/>
<field name="employee_id"/>
<field name="employee_number" column_invisible="1"/>
<field name="company_id" readonly="1"/>
<field name="department_id" readonly="1"/>
<field name="notice_id"/>
<field name="notice_code"/>
<field name="state"/>
<field name="release_date"/>
<field name="create_time"/>
<field name="msg_id"/>
<button name="withdrawn" string="撤回" type="object" class="oe_highlight" confirm="确认要撤回吗?"/>
</list>
</field>
</record>
@ -32,7 +40,7 @@
<record id="yuthon_confirm_users_action" model="ir.actions.act_window">
<field name="name">公告确认信息</field>
<field name="res_model">yuthon.confirm.users</field>
<field name="view_mode">list</field>
<field name="view_mode">tree</field>
</record>
</odoo>

View File

@ -9,9 +9,9 @@
</list>
</field>
</record>
<record id="action_yuthon_cs_tree" model="ir.actions.act_window">
<field name="name">Yuthon CS Tree</field>
<field name="res_model">yuthon.cs.tree</field>
<record id="action_yuthon_cs_list" model="ir.actions.act_window">
<field name="name">Yuthon CS list</field>
<field name="res_model">yuthon.cs.list</field>
<field name="view_mode">list</field>
</record>
</odoo>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_notice_type_tree" model="ir.ui.view">
<field name="name">yuthon.notice.type.tree</field>
<record id="view_yuthon_notice_type_list" model="ir.ui.view">
<field name="name">yuthon.notice.type.list</field>
<field name="model">yuthon.notice.type</field>
<field name="arch" type="xml">
<list string="公告类型" editable="bottom">

View File

@ -1,24 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_notice_tree" model="ir.ui.view">
<field name="name">yuthon.notice.tree</field>
<record id="view_yuthon_notice_list" model="ir.ui.view">
<field name="name">yuthon.notice.list</field>
<field name="model">yuthon.notice</field>
<field name="arch" type="xml">
<list string="公告管理" decoration-success="notice_state == 'yes'"
decoration-info="notice_state == 'no'"
decoration-muted="notice_state == 'stop'">
decoration-muted="notice_state == 'stop'" default_order="create_date1 desc">
<field name="name" required="1"/>
<field name="notice_type_id" string="类型" class="list_ids_width"/>
<field name="users_id" string="发布人" class="list_ids_width"/>
<field name="notice_type_id" string="类型"/>
<field name="users_id" string="发布人"/>
<field name="department_id" column_invisible="1"/>
<field name="users_ids" column_invisible="1"/>
<field name="create_date1"/>
<field name="end_date"/>
<field name="notice_state" class="list_ids_width"/>
<button name="on_sequence" string="置顶" invisible="sequence != 0" type="object" class="btn-primary"/>
<button name="no_sequence" string="取消置顶" invisible="sequence != 1" type="object" class="btn-primary"/>
<!-- <button name="look_up_notice_users" string="查阅情况" type="object" class="btn-primary"/>-->
<button name="stop_notice" string="终止" type="object" class="btn-primary"/>
<field name="notice_state"/>
<button name="on_sequence" string="置顶" invisible="sequence != 0" type="object" class="btn btn-blue"/>
<button name="no_sequence" string="取消置顶" invisible="sequence != 1" type="object" class="btn btn-blue"/>
<button name="look_up_notice_users" string="查阅情况" type="object" class="btn btn-blue"/>
<button name="stop_notice" string="终止" type="object" class="btn btn-blue"/>
<field name="sequence" column_invisible="1"/>
</list>
</field>
@ -29,13 +29,10 @@
<field name="model">yuthon.notice</field>
<field name="arch" type="xml">
<form string="公告管理" create="0">
<header>
<button name="action_print_notice" string="打印" type="object" class="btn-primary" invisible="not work_end"/>
</header>
<sheet>
<widget name="web_ribbon" title="普件" bg_color="text-bg-success"
invisible="urgency_level != 'normal'"/>
<widget name="web_ribbon" title="急件" bg_color="text-bg-warning"
invisible="urgency_level != 'urgent'"/>
<widget name="web_ribbon" title="特级" bg_color="text-bg-danger"
invisible="urgency_level != 'emergency'"/>
<group col="4">
<group>
<field name="code" string="流水号" readonly="1"/>
@ -57,12 +54,14 @@
</div>
<group col="4">
<group>
<field name="notice_type_id"/>
<!-- <field name="publish_date"/>-->
<field name="notice_type_id" required="1"/>
</group>
<group>
<field name="users_id"/>
<field name="users_id" options="{'no_create': True}"/>
</group>
<group>
<field name="self_users_ids" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="work_end" invisible="1"/>
<field name="department_id" string="发布部门" invisible="1"/>
@ -71,23 +70,18 @@
</group>
<group>
<field name="name" required="1"/>
<field name="notice_text" widget="tinymce"/>
<field name="documents_ids" widget="preview_many2many"/>
</group>
<group col="4" string="发布范围">
<group>
<field name="department_ids" widget="many2many_tags" context="{'tree_view_ref':'yuthon_notice.view_yuthon_cs_tree'}" domain = "[('company_id', '=', company_id)] if is_this_company else []" options="{'no_create': True}"/>
</group>
<group>
<field name="users_role_group_ids" widget="many2many_tags" options="{'no_create': True}"/>
</group>
<group>
<field name="users_role_ids" widget="many2many_tags" options="{'no_create': True}"/>
</group>
<group>
<field name="employee_ids" widget="many2many_tags" context="{'tree_view_ref':'yuthon_notice.view_yuthon_cs_tree'}" options="{'no_create': True}" domain = "[('company_id', '=', company_id), ('user_id.name', '!=', 'Administrator')] if is_this_company else []"/>
<field name="users_ids" invisible="1" widget="many2many_tags"/>
</group>
<group>
<field name="notice_text" widget="tinymce" invisible="1"/>
<field name="notice_text1"/>
</group>
<!--<group col="3" string="发布范围">-->
<group string="发布范围">
<field name="department_ids" widget="many2many_tags" domain="[('company_id', '=', company_id), ('name', 'not like', '天河投资集团')] if is_this_company else [('name', 'not like', '天河投资集团')]" options="{'no_create': True}"/>
</group>
<group>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}" domain="[('company_id', '=', company_id), ('user_id.name', '!=', 'Administrator')] if is_this_company else []"/>
<field name="users_ids" widget="many2many_tags" invisible="1"/>
</group>
<group col="3">
<group>
@ -105,17 +99,17 @@
</field>
</record>
<record id="view_yuthon_notice_tree2" model="ir.ui.view">
<field name="name">yuthon.notice.tree</field>
<record id="view_yuthon_notice_list2" model="ir.ui.view">
<field name="name">yuthon.notice.list</field>
<field name="model">yuthon.notice</field>
<field name="arch" type="xml">
<list string="公告管理" edit="false" delete="false" create="false"
decoration-danger="is_notice_roger == 'no'"
decoration-success="is_notice_roger == 'yes'"
js_class="custom_notice_tree_button">
js_class="custom_notice_tree_button" default_order="create_date1 desc">
<field name="name"/>
<field name="notice_type_id" string="类型"/>
<field name="users_id" string="发布人" class="list_ids_width"/>
<field name="users_id" string="发布人"/>
<field name="is_notice_roger"/>
<field name="department_id" column_invisible="1"/>
<field name="users_ids" column_invisible="1"/>
@ -135,7 +129,7 @@
<field name="notice_type_id" string="发布类型"/>
</group>
<group>
<field name="users_id"/>
<field name="users_id" options="{'no_create': True}"/>
</group>
<group>
<field name="read_number"/>
@ -153,8 +147,8 @@
</h1>
</div>
<group>
<field name="notice_text" widget="tinymce" string=" "/>
<field name="documents_ids" widget="preview_many2many"/>
<field name="notice_text" widget="tinymce" string=" " invisible="1"/>
<field name="notice_text1"/>
</group>
</form>
</field>
@ -166,9 +160,8 @@
<field name="arch" type="xml">
<search string="公告查询">
<field string="标题" name="name"/>
<field string="发布人" name="users_id"/>
<field string="正文" name="notice_text"/>
<field string="附件" name="documents_ids"/>
<field string="发布人" name="users_id" options="{'no_create': True}"/>
<field string="正文" name="notice_text1"/>
<field string="部门" name="department_id"/>
<filter string="未发布" name="no " domain="[('notice_state', '=', 'no')]"/>
<filter string="已发布" name="done " domain="[('notice_state', '=', 'done')]"/>
@ -183,18 +176,19 @@
<record id="approval_notice_action" model="ir.actions.act_window">
<field name="name">通知公告</field>
<field name="domain">[('users_ids','in',uid),('notice_state','=','yes')]</field>
<field name="res_model">yuthon.notice</field>
<field name="view_mode">list,form</field>
<field name="domain">[('notice_state', '=', 'yes'),('users_ids', 'in', [uid])]</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'list', 'view_id': ref('yuthon_notice.view_yuthon_notice_tree2')}),
(0, 0, {'view_mode': 'list', 'view_id': ref('yuthon_notice.view_yuthon_notice_list2')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_notice.view_yuthon_notice_form2')})]"/>
</record>
<record id="approval_notice_action2" model="ir.actions.act_window">
<field name="name">通知公告管理</field>
<field name="domain">[('is_company', '=', True)]</field>
<field name="res_model">yuthon.notice</field>
<field name="view_mode">list,form</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="yuthon_notice_menu_config_root"

View File

@ -4,13 +4,12 @@
'summary': "日程事项",
'description': """""",
'author': 'zhou',
'version': '18.0.1.0.0',
'version': '0.1',
'license': 'LGPL-3',
'data': [
# 'data/will_task_time_data.xml',
# 'data/task_code_data.xml',
# 'data/mail_template.xml',
# 'data/tsak_time_data.xml',
'data/task_code_data.xml',
'data/mail_template.xml',
'data/tsak_time_data.xml',
'security/yuthon_will_task_security.xml',
'security/ir.model.access.csv',
'views/yuthon_will_task_views.xml',
@ -25,7 +24,7 @@
],
},
'demo': [],
'depends': ['hr', 'mail'],
'depends': ['hr', 'mail', 'yuthon_hr_employee', 'room'],
'installable': True,
'application': True,
}

View File

@ -1 +1 @@
from . import main
from . import main

View File

@ -1,62 +1,42 @@
import base64
import json
from datetime import timedelta
from odoo import http, fields, Command
from odoo import http, fields
from odoo.http import request
from odoo.osv import expression
import logging
_logger = logging.getLogger(__name__)
from odoo.tools import float_is_zero,float_round
from odoo.exceptions import UserError
from datetime import datetime
from urllib.parse import quote
class YuthonWillTask(http.Controller):
@http.route('/yuthon/yuthon/will/task', methods=['POST'], type='http', auth='none', csrf=False)
def index2(self, **kwargs):
"""根据ID获取日程详情"""
id = kwargs.get('id')
data = request.env['yuthon.will.task'].sudo().search(domain=[("id", "=", int(id))])
request.env['yuthon.will.task'].create({
'fields': kwargs.get('id'),
})
file_list = []
pdf = ['application/pdf']
for file in data.document_ids:
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
file_url = f"{base_url}/web/content/preview/{file.id}"
if file.mimetype in pdf:
file_url = f"{base_url}/web/content/preview/{file.id}"
file_list.append({
'file_url': file_url,
'file_name': file.name,
})
data = request.env['yuthon.will.task'].sudo().search(domain = [("id", "=", int(id))])
employee_ids = []
for em in data.employee_ids:
employee_ids.append({
'id': em.id,
'name': em.name,
'image_1920': em.image_128.decode('utf-8') if em.image_128 else '',
'id':em.id,
'name':em.name,
'image_1920':em.image_128.decode('utf-8') if em.image_128 else '',
})
result = {
'id': data.id,
'name': data.name,
'code': data.code,
'start_date': fields.Date.to_string(data.start_date) if data.start_date else '',
'request_hour_from': data.request_hour_from,
'request_hour_to': data.request_hour_to,
'priority': data.priority,
'user_id': data.user_id.name,
'user_avatar': base64.b64decode(data.user_id.avatar_128).decode('utf-8') if data.user_id.avatar_128 else '',
'remarks': data.remarks,
'address': data.address,
'employee_ids': employee_ids,
'file_list': file_list,
'id':data.id,
'name':data.name,
'code':data.code,
'start_date':fields.Date.to_string(data.start_date),
'request_hour_from':data.request_hour_from,
'request_hour_to':data.request_hour_to,
'priority':data.priority,
'user_id':data.user_id.name,
'user_avatar':base64.b64decode(data.user_id.avatar_128).decode('utf-8') if data.user_id.avatar_128 else '',
'remarks':data.remarks,
'address':data.address,
'employee_ids':employee_ids,
}
# 转换 request_hour_from 和 request_hour_to 的显示值
# 转换 request_hour_from 和 request_hour_to 取模型 yuthon.will.task 的 request_hour_from 的selection
request_hour_from_selection = request.env['yuthon.will.task'].sudo().fields_get(allfields=['request_hour_from'])['request_hour_from']['selection']
request_hour_to_selection = request.env['yuthon.will.task'].sudo().fields_get(allfields=['request_hour_to'])['request_hour_to']['selection']
@ -68,8 +48,7 @@ class YuthonWillTask(http.Controller):
result['request_hour_to'] = f[1]
# 计算 start_date 星期几
if data.start_date:
result['start_date_week'] = data.start_date.strftime('%A')
result['start_date_week'] = fields.Date.from_string(result['start_date']).strftime('%A')
return json.dumps({
'data': result,
@ -77,54 +56,101 @@ class YuthonWillTask(http.Controller):
'message': 'success'
})
@http.route('/task/<int:task_id>', type='http', auth='user', website=False)
def open_task(self, task_id, **kwargs):
"""通过ID打开特定任务的详情页"""
# 查询任务数据(带权限校验)
Task = request.env['yuthon.will.task']
task = Task.sudo().browse(task_id)
if not task.exists():
return request.not_found("任务不存在或已被删除")
# 格式化时间显示将0.5转为0:30格式
def format_time(hour_str):
if not hour_str:
return ""
if hour_str.endswith('.5'):
return f"{int(float(hour_str))}:30"
return f"{int(float(hour_str))}:00"
# 准备模板所需数据
task_data = {
'task': task,
'format_time': format_time, # 传递格式化函数到模板
'employee_names': ', '.join(task.employee_ids.mapped('name')),
'driver_names': ', '.join(task.employee_ids2.mapped('name')) or '',
}
@http.route('/yuthon/task/detail/<int:task_id>',type='http',auth='public',website=False)
def task_detail(self, task_id, **kw):
try:
task = request.env['yuthon.will.task'].sudo().browse(task_id)
if not task.exists():
raise MissingError(f"任务ID {task_id} 不存在")
participant_names = []
if task.employee_ids:
participant_names.extend(task.employee_ids.mapped('name'))
time_display = ""
if task.start_date:
time_display = str(task.start_date)
task_data = {
'name': task.name or '',
'start_date': time_display,
'address': task.address or '',
'user_id': task.user_id.name if task.user_id else '',
'employee_ids': ', '.join(participant_names) if participant_names else '',
'code': task.code or '',
'request_hour_from': task.request_hour_from,
'request_hour_to': task.request_hour_to,
}
return request.render('yuthon_will_task.task_detail_template', task_data)
except MissingError as e:
return f"<h3>错误:{str(e)}</h3>"
except Exception as e:
return f"<h3>加载失败:{str(e)}</h3>"
@http.route('/will/task/list', methods=['POST'], type='http', auth='public', csrf=False)
def will_task_list(self, **kwargs):
"""获取日程列表(手机端接口)"""
start = int(kwargs.get('start'))
users_id = int(kwargs.get('users_id'))
_filter = kwargs.get('filter')
state = kwargs.get('state')
order = 'task_datetime desc' if state == 'done' else 'task_datetime asc'
domain = [('users_ids', 'in', [users_id])]
today = fields.Date.today()
if _filter == 'today':
domain = expression.AND([domain, [('start_date', '=', today)]])
elif _filter == 'week':
start_date = today - timedelta(days=today.weekday())
end_date = start_date + timedelta(days=6)
domain = expression.AND([domain, [('start_date', '>=', start_date), ('start_date', '<=', end_date)]])
domain = expression.AND([domain, [('task_state', '=', state)]])
WillTask = request.env['yuthon.will.task'].sudo()
request_hour_selection = WillTask.fields_get(allfields=['request_hour_from'])['request_hour_from']['selection']
state_dict = dict(WillTask.fields_get(allfields=['task_state'])['task_state']['selection'])
request_hour_selection = request.env['yuthon.will.task'].sudo().fields_get(allfields=['request_hour_from'])['request_hour_from']['selection']
employee_id = kwargs.get('employee_id')
data = request.env['yuthon.will.task'].sudo().search(
['|', ('employee_ids', 'in', [employee_id]), ('employee_id.id', '=', employee_id)])
result_list = []
task_ids = request.env['yuthon.will.task'].sudo().search(domain, offset=start, limit=20, order=order)
user_id = request.env['res.users'].sudo().search([('id', '=', users_id)])
company_id = user_id.company_id
for task in task_ids:
if company_id.id == task.company_id.id:
weekday_str = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][task.start_date.weekday()]
result = {
'id': task.id,
'name': task.name,
'employee_id': task.employee_id.name or '',
'employee_ids': ', '.join(task.employee_ids.mapped('name')) if task.employee_ids else '',
'employee_num': ','.join(task.employee_ids.mapped('name')) if task.employee_ids else '',
'start_date': fields.Date.to_string(task.start_date) if task.start_date else '',
'weekday': weekday_str,
'address': task.address or '',
'request_hour_from': '',
'request_hour_to': '',
'state': task.task_state,
'display_state': state_dict.get(task.task_state),
'task_type': task.task_type
}
hour_dict = dict(request_hour_selection)
result['request_hour_from'] = hour_dict.get(task.request_hour_from)
result['request_hour_to'] = hour_dict.get(task.request_hour_to)
result_list.append(result)
for task in data:
weekday_str = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][task.start_date.weekday()]
file_list = []
for attach in task.document_ids:
attach_id = request.env['ir.attachment'].sudo().search([('id', '=', attach.id)])
file_url = 'http://view.officeapps.live.com/op/view.aspx?src=' + quote(
'https://oa.thtzjt.com' + '/web/content/office_preview/' + str(attach_id.id))
file_list.append({
'file_url': file_url,
'file_name': attach_id.display_name,
})
result = {
'id': task.id,
'name': task.name,
'employee_id': task.employee_id.name,
'employee_ids': ', '.join(task.employee_ids.mapped('name')),
'start_date': fields.Date.to_string(task.start_date),
'weekday': weekday_str,
'request_hour_from': '',
'request_hour_to': '',
'file_list': file_list,
}
for f in request_hour_selection:
if f[0] == task.request_hour_from:
result['request_hour_from'] = f[1]
if f[0] == task.request_hour_to:
result['request_hour_to'] = f[1]
result_list.append(result)
return json.dumps({
'data': result_list,
'code': 200,
@ -133,41 +159,33 @@ class YuthonWillTask(http.Controller):
@http.route('/will/task/record', methods=['POST'], type='http', auth='public', csrf=False)
def will_task_record(self, **kwargs):
"""获取单个日程记录详情"""
task_id = kwargs.get('id')
task = request.env['yuthon.will.task'].sudo().search([('id', '=', task_id)])
task = request.env['yuthon.will.task'].sudo().search([('id', '=', task_id)], limit=1)
request_hour_selection = request.env['yuthon.will.task'].sudo().fields_get(allfields=['request_hour_from'])['request_hour_from']['selection']
file_list = []
pdf = ['application/pdf']
for file in task.document_ids:
base_url = request.env['ir.config_parameter'].sudo().get_param('web.base.url')
file_url = f"{base_url}/web/content/preview/{file.id}"
if file.mimetype in pdf:
file_url = f"{base_url}/web/content/preview/{file.id}"
file_list.append({
'file_url': file_url,
'file_name': file.name,
})
for attach in task.document_ids:
attach_id = request.env['ir.attachment'].sudo().search([('id', '=', attach.id)], limit=1)
if attach_id:
file_url = 'http://view.officeapps.live.com/op/view.aspx?src=' + quote(
'https://oa.thtzjt.com' + '/web/content/office_preview/' + str(attach_id.id))
file_list.append({
'file_url': file_url,
'file_name': attach_id.display_name,
})
weekday_str = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"][task.start_date.weekday()]
result = {
'id': task.id,
'name': task.name,
'employee_id': task.employee_id.name,
'employee_ids': ','.join(task.employee_ids.mapped('name')),
'task_time_ids': ','.join(task.task_time_ids.mapped('name')),
'employee': task.employee_ids.read(['name']),
'task_time': task.task_time_ids.read(['name']),
'employee_num': len(task.employee_ids),
'start_date': fields.Date.to_string(task.start_date) if task.start_date else '',
'employee_ids': ', '.join(task.employee_ids.mapped('name')),
'start_date': fields.Date.to_string(task.start_date),
'weekday': weekday_str,
'request_hour_from': task.request_hour_from,
'request_hour_to': task.request_hour_to,
'remarks': task.remarks,
'address': task.address,
'request_hour_from': '',
'request_hour_to': '',
'file_list': file_list,
'priority': task.priority,
}
hour_dict = dict(request_hour_selection)
result['request_hour_from'] = hour_dict.get(task.request_hour_from)
result['request_hour_to'] = hour_dict.get(task.request_hour_to)
@ -177,106 +195,4 @@ class YuthonWillTask(http.Controller):
'message': 'success'
})
@http.route('/will/task/create', methods=['POST'], type='http', auth='public', csrf=False)
def will_task_create(self, **kwargs):
"""手机端创建日程接口"""
request.env['yuthon.will.task'].sudo().create({
'name': kwargs.get('name'),
'employee_id': kwargs.get('id'),
'employee_ids': kwargs.get('id'),
'start_date': kwargs.get('id'),
'request_hour_from': '',
'request_hour_to': '',
'remarks': kwargs.get('remarks'),
'address': kwargs.get('address'),
})
return json.dumps({
'code': 200,
'message': '数据创建成功'
})
@http.route('/phone/employee/self', type='http', auth="none", csrf=False, cors='*')
def phone_employee_self(self, **kw):
"""搜索员工(手机端接口)"""
value = kw.get('value', False)
domain = []
if value:
domain.append(('name', 'ilike', value))
records = request.env['hr.employee'].sudo().search(domain, limit=15)
data = records.read(['name'])
return json.dumps({'data': data, 'code': 200, 'message': ''})
@http.route('/phone/task/time', type='http', auth="none", csrf=False, cors='*')
def phone_task_time(self, **kw):
"""搜索提醒时间配置(手机端接口)"""
value = kw.get('value', False)
domain = []
if value:
domain.append(('name', 'ilike', value))
records = request.env['yuthon.task.time'].sudo().search(domain, limit=15)
data = records.read(['name'])
return json.dumps({'data': data, 'code': 200, 'message': ''})
@http.route('/phone/task/create', type='http', auth="none", csrf=False, cors='*')
def phone_task_create(self, **kw):
"""手机端创建/更新日程接口
record_id: 记录id根据这个判断是否创建还是更新创建的时候不传更新的时候传
request_hour_from: 开始时间
request_hour_to: 结束时间
priority: 优先级
name: 任务内容
remarks: 备注
employee_ids: 关联员工
task_time_ids: 提醒
address: 地址
start_date: 任务日期
"""
def str2list(value: str) -> list:
if not value:
return []
if not isinstance(value, str):
raise ValueError('value must be str')
str_list = value.split(',')
return [int(s) for s in str_list]
employee_ids = str2list(kw.get('employee_ids'))
task_time_ids = str2list(kw.get('task_time_ids'))
if not isinstance(task_time_ids, list):
return json.dumps({'data': '', 'code': 201, 'message': '提醒参数错误'})
if not isinstance(employee_ids, list):
return json.dumps({'data': '', 'code': 201, 'message': '关联员工参数错误'})
data = {
'request_hour_from': kw.get('request_hour_from', False),
'request_hour_to': kw.get('request_hour_to', False),
'priority': kw.get('priority', False),
'name': kw.get('name', False),
'remarks': kw.get('remarks', False),
'employee_ids': [(6, 0, employee_ids)],
'task_time_ids': [(6, 0, task_time_ids)],
'address': kw.get('address', False),
'start_date': kw.get('start_date', False)
}
Task = request.env['yuthon.will.task'].sudo()
record_id = kw.get('record_id', False)
if record_id:
# 更新日程
record = Task.browse(int(record_id))
record.write(data)
else:
# 创建日程
users_id = int(kw.get('users_id'))
user = request.env['res.users'].sudo().browse(int(users_id))
record = Task.with_user(user).sudo().create(data)
return json.dumps({'data': record.id, 'code': 200, 'message': ''})
@http.route('/phone/task/unlink', type='http', auth="none", csrf=False, cors='*')
def phone_task_unlink(self, **kw):
"""手机端删除日程接口"""
record_id = kw.get('record_id', False)
if record_id:
record = request.env['yuthon.will.task'].sudo().browse(int(record_id))
record.sudo().unlink()
else:
return json.dumps({'data': '', 'code': 201, 'message': '记录不存在'})
return json.dumps({'data': 'success', 'code': 200, 'message': ''})

View File

@ -1,3 +1,4 @@
from . import yuthon_will_task
from . import yuthon_task_time
from . import inherit_room_booking
from . import yuthon_dates_task

View File

@ -0,0 +1,116 @@
from odoo import api, fields, models
from datetime import datetime, timedelta, date
import logging
_logger = logging.getLogger(__name__)
class InheritRoomBooking(models.Model):
_inherit = 'room.booking'
def get_no_read_count(self):
# 1. 计算各类时间参数
today = date.today() + timedelta(hours=8)
today_start = datetime.combine(today, datetime.min.time())
today_end = datetime.combine(today, datetime.max.time())
domain = [
('start_datetime', '>=', today_start),
('start_datetime', '<=', today_end),
'|',
('stop_datetime', '=', False),
('stop_datetime', '>', datetime.now())
]
matching_records = self.search(domain)
count = len(matching_records)
return count
@api.model
def create(self, vals):
new_record = super(InheritRoomBooking, self).create(vals)
employee_list = []
booking = new_record
# ① 组织者
if booking.organizer_id:
employee_list.append(booking.organizer_id.employee_id.id)
# ② 记录人
if booking.user_id.employee_id:
employee_list.append(booking.user_id.employee_id.id)
# ③ 出席人员
if booking.room_users_ids:
for user in booking.room_users_ids:
if user.employee_id:
employee_list.append(user.employee_id.id)
# ④ 议题传达人
for line in booking.issue_meeting_line_ids:
if line.employee_id:
employee_list.append(line.employee_id.id)
# ⑤ 列席人员
for line in booking.issue_meeting_line_ids:
if line.attendees_ids:
employee_list.extend(line.attendees_ids.ids)
# ⑥ 列席角色群组
for line in booking.issue_meeting_line_ids:
if line.users_role_group_ids:
role_group_roles = line.users_role_group_ids.mapped('user_role_ids')
role_employees = self.env['hr.employee'].search([
'|',
('user_role_id', 'in', role_group_roles.ids),
('user_ids', 'in', role_group_roles.ids)
])
employee_list.extend(role_employees.ids)
# ⑦ 主持人
if booking.host:
employee_list.append(booking.host.id)
# 去重
employee_list = list(set(employee_list))
def get_hour_selection(datetime_obj):
if not datetime_obj:
return None
datetime_plus_8h = datetime_obj + timedelta(hours=8)
hour = datetime_plus_8h.hour
minute = datetime_plus_8h.minute
if minute == 0:
return str(hour)
elif minute == 15:
return f"{hour}.25"
elif minute == 30:
return f"{hour}.5"
elif minute == 45:
return f"{hour}.75"
else:
nearest_minute = round(minute / 15) * 15
if nearest_minute == 60:
hour += 1
nearest_minute = 0
if nearest_minute == 0:
return str(hour % 24)
elif nearest_minute == 15:
return f"{hour % 24}.25"
elif nearest_minute == 30:
return f"{hour % 24}.5"
elif nearest_minute == 45:
return f"{hour % 24}.75"
start_datetime = new_record.start_datetime
start_date = start_datetime.date() if start_datetime else False
request_hour_from = get_hour_selection(new_record.start_datetime)
request_hour_to = get_hour_selection(new_record.stop_datetime)
task_time_vals = vals.get('task_time_ids', [])
task_id = self.env['yuthon.will.task'].sudo().create({
'name': new_record.name,
'employee_ids': [(6, 0, employee_list)],
'task_time_ids': task_time_vals,
'start_date': start_date,
'request_hour_from': request_hour_from,
'request_hour_to': request_hour_to,
'address': new_record.room_id.name if new_record.room_id else '',
'room_booking_id': new_record.id,
'task_type': 'meet',
'task_state': 'will',
})
task_id._onchange_employee_ids()
return new_record

View File

@ -1,4 +1,12 @@
from odoo import api, fields, models
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import qrcode
import base64
from io import BytesIO
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError, UserError
import os
from datetime import timedelta
class YuthonDatesTask(models.Model):
@ -9,3 +17,4 @@ class YuthonDatesTask(models.Model):
yuthon_will_task_id = fields.Many2one('yuthon.will.task', string='日程')
employee_ids = fields.Many2many('hr.employee', string='提醒人员')
task_state = fields.Selection([('to_do', '待提醒'), ('done', '已提醒')], string='状态')

View File

@ -13,7 +13,6 @@ class YuthonTaskTime(models.Model):
@api.onchange('number', 'task_unit')
def onchange_name(self):
"""根据数值和单位自动生成名称"""
task_unit_dict = {'min': '分钟', 'hour': '小时', 'day': ''}
for n in self:
if n.number and n.task_unit:

View File

@ -1,5 +1,6 @@
from odoo import api, fields, models
from datetime import datetime, timedelta, time, date
from odoo import api, fields, models, modules
import datetime
from datetime import datetime, timedelta, time
from odoo.exceptions import UserError, ValidationError
import logging
@ -11,7 +12,6 @@ class YuthonWillTask(models.Model):
_description = '日程'
_rec_name = 'name'
_order = 'task_datetime desc'
_inherit = ['mail.thread', 'mail.activity.mixin']
time_list = [
('0', '0:00'), ('0.25', '0:15'), ('0.5', '0:30'), ('0.75', '0:45'),
@ -38,16 +38,15 @@ class YuthonWillTask(models.Model):
('21', '21:00'), ('21.25', '21:15'), ('21.5', '21:30'), ('21.75', '21:45'),
('22', '22:00'), ('22.25', '22:15'), ('22.5', '22:30'), ('22.75', '22:45'),
('23', '23:00'), ('23.25', '23:15'), ('23.5', '23:30'), ('23.75', '23:45')]
def _default_request_hour_from(self):
"""开始时间默认当前小时处理UTC+8时区"""
now = fields.Datetime.now() + timedelta(hours=8)
now = fields.datetime.now() + timedelta(hours=8)
hour = now.hour
return str(hour)
def _default_request_hour_to(self):
"""结束时间默认当前小时+123+1=0处理UTC+8时区"""
now = fields.Datetime.now() + timedelta(hours=8)
now = fields.datetime.now() + timedelta(hours=8)
hour = (now.hour + 1) % 24
return str(hour)
@ -63,6 +62,9 @@ class YuthonWillTask(models.Model):
creation_time = fields.Date(string='创建时间', default=fields.Date.today())
employee_ids = fields.Many2many('hr.employee', 'will_task_employee_rel', 'custom_id', 'employee_id',
string='人员', store=True, index=True)
employee_ids2 = fields.Many2many('hr.employee', 'will_task_employee_rel2', 'custom_id', 'employee_id',
string='司机', store=True, index=True)
user_role_ids = fields.Many2many('soong.user.role', string='角色', store=True, index=True)
task_time_ids = fields.Many2many('yuthon.task.time', string='提醒')
document_ids = fields.Many2many('ir.attachment', string="附件")
address = fields.Char(string='地点')
@ -77,54 +79,82 @@ class YuthonWillTask(models.Model):
string='用户', store=True, index=True, default=lambda self: self.env.user)
self_users_ids = fields.Many2many('res.users',
relation='yuthon_will_task_current_users_rel', column1='task_id', column2='user_id', string="当前经办人", store=True, default=lambda self: [(6, 0, [self.env.user.id])]
relation='yuthon_will_task_current_users_rel',column1='task_id', column2='user_id', string="当前经办人", store=True, default=lambda self: [(6, 0, [self.env.user.id])]
)
employee_id = fields.Many2one('hr.employee', string="经办人", default=lambda self: self.env.user.employee_id)
task_week = fields.Char(string="", readonly=True, compute='_compute_task_week')
task_week2 = fields.Char(string="", readonly=True, compute='_compute_task_week2')
msg_id = fields.Char(string="msg_id")
task_type = fields.Selection([
('task', '日程'),
('meet', '会议')
], string='类型', default='task')
room_booking_id = fields.Many2one('room.booking', string='会议室')
is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company',
search='_search_part_of_company')
def update_task_state(self):
"""将选中日程标记为已办"""
will_ids = self.env["yuthon.will.task"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for will in will_ids:
will.task_state = 'done'
def _compute_is_company(self):
active_company_ids = self.env.companies
for employee in self:
employee.is_company = employee.company_id in active_company_ids
def _search_part_of_company(self, operator, value):
company_ids = self.env.companies.ids
if not value:
operator = '!=' if operator == '=' else '='
if operator == '=':
return [('company_id', 'in', company_ids)]
else:
return [('company_id', 'not in', company_ids)]
@api.onchange('start_date', 'end_date')
def _onchange_date_range(self):
"""校验开始日期不能大于结束日期"""
if self.start_date and self.end_date:
if self.start_date > self.end_date:
start = fields.Datetime.from_string(self.start_date)
end = fields.Datetime.from_string(self.end_date)
if start > end:
raise ValidationError("开始日期必须小于结束日期,请重新设置。")
def back_wecom_msg(self):
self.env['wecom.apps'].back_message(category='task', msg_id=self.msg_id)
def open_record(self):
"""打开当前日程记录的表单视图"""
self.ensure_one()
if self.room_booking_id:
target_model = 'room.booking'
target_id = self.room_booking_id.id
view_id = self.env.ref('room.room_booking_view_form').id
else:
target_model = self._name
target_id = self.id
view_id = self.env.ref('yuthon_will_task.view_yuthon_will_task_form').id
return {
'type': 'ir.actions.act_window',
'name': '日程详情',
'res_model': self._name,
'res_id': self.id,
'name': f'{target_model} 详情',
'res_model': target_model,
'res_id': target_id,
'view_mode': 'form',
'view_id': self.env.ref('yuthon_will_task.view_yuthon_will_task_form').id,
'view_type': 'form',
'view_id': view_id,
'target': 'current',
}
@api.depends('employee_ids')
def _compute_employee_ids_short(self):
"""计算参与人简略显示最多2人"""
for record in self:
names = record.employee_ids.mapped('name')[:2]
record.employee_ids_short = ''.join(names)
@api.depends('start_date')
def _compute_task_week(self):
"""计算开始日期对应的星期"""
weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
for record in self:
if record.start_date:
@ -132,14 +162,12 @@ class YuthonWillTask(models.Model):
@api.depends('end_date')
def _compute_task_week2(self):
"""计算结束日期对应的星期"""
weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
for record in self:
if record.end_date:
record.task_week2 = weekdays[record.end_date.weekday()]
def one_time(self):
"""手动触发计算任务时间"""
will_ids = self.env["yuthon.will.task"].browse(
self._context.get('active_ids', self._context.get('active_id')))
for record in will_ids:
@ -166,9 +194,8 @@ class YuthonWillTask(models.Model):
else:
record.task_datetime = False
@api.depends('start_date', 'task_week', 'request_hour_from', 'task_type')
@api.depends('start_date', 'task_week', 'request_hour_from', 'task_type', 'room_booking_id', 'room_booking_id.start_datetime', 'room_booking_id.stop_datetime')
def _compute_task_time(self):
"""计算任务时间显示文本"""
for record in self:
date_str = ""
if record.start_date:
@ -201,28 +228,38 @@ class YuthonWillTask(models.Model):
}
from_time = hour_mapping.get(record.request_hour_from, "")
if date_str and from_time:
base_time = f"{date_str} {record.task_week} {from_time}"
record.task_time = base_time
# 会议类型:显示完整的开始和结束日期时间
if record.task_type == 'meet' and record.room_booking_id and record.room_booking_id.start_datetime and record.room_booking_id.stop_datetime:
# 转换为本地时间UTC+8
start_local = record.room_booking_id.start_datetime + timedelta(hours=8)
stop_local = record.room_booking_id.stop_datetime + timedelta(hours=8)
# 格式化为 "YYYY-M-D HHMM" 格式
start_str = start_local.strftime("%Y-%m-%d %H%M")
stop_str = stop_local.strftime("%Y-%m-%d %H%M")
record.task_time = f"{start_str}{stop_str}"
else:
base_time = f"{date_str} {record.task_week} {from_time}"
record.task_time = base_time
else:
record.task_time = ""
@api.onchange('employee_ids', 'user_id')
def _onchange_employee_ids(self):
"""人员变更时同步更新关联用户列表"""
user_ids = []
if self.user_id:
user_ids.append(self.user_id.id)
for employee in self.employee_ids:
if employee.user_id:
user_ids.append(employee.user_id.id)
for employee2 in self.employee_ids2:
if employee2.user_id:
user_ids.append(employee2.user_id.id)
self.users_ids = [(6, 0, user_ids)]
def read_number(self):
"""获取当前用户的待办日程数量"""
return self.search_count([('task_state', '=', 'will'), ('users_ids', 'in', self.env.user.id)])
def _compute_duration(self, task_time):
"""根据提醒时间配置计算时间差"""
if task_time.task_unit == 'min':
return timedelta(minutes=task_time.number)
elif task_time.task_unit == 'hour':
@ -233,8 +270,7 @@ class YuthonWillTask(models.Model):
return timedelta.max
def add_done(self):
"""定时任务:将过期的待办日程自动标记为已办"""
today = date.today()
today = datetime.date.today()
records = self.search([
('task_state', '=', 'will'),
('end_date', '<', today),
@ -244,58 +280,57 @@ class YuthonWillTask(models.Model):
records.write({'task_state': 'done'})
def add_comment(self):
"""日程定时任务提醒——通过 Odoo 内部消息通知"""
"""日程定时任务提醒"""
_logger.info('-----------日程任务验证--------')
now = fields.Datetime.now() + timedelta(hours=8)
dates_task_ids = self.env['yuthon.dates.task'].search([
('task_state', '=', 'to_do'),
('reminder_time', '<', fields.Datetime.now() + timedelta(days=1))
])
now = fields.datetime.now() + timedelta(hours=8)
today = fields.date.today()
dates_task_ids = self.env['yuthon.dates.task'].search([('task_state', '=', 'to_do'), ('reminder_time', '<', fields.datetime.now())])
for task in dates_task_ids:
hour1 = (task.reminder_time + timedelta(hours=8)).hour
minute1 = (task.reminder_time + timedelta(hours=8)).minute
_logger.info(f'-任务时间{hour1}:{minute1}当前时间{now.hour}:{now.minute}')
reminder_local = task.reminder_time + timedelta(hours=8)
hour1 = reminder_local.hour
minute1 = reminder_local.minute
_logger.info(f'-任务时间{hour1}:{minute1}任务分钟{now.hour}:{now.minute}')
_logger.info(f'-状态{minute1 == now.minute and hour1 == now.hour}')
if minute1 == now.minute and hour1 == now.hour and task.reminder_time.day == fields.Date.today().day:
if minute1 == now.minute and hour1 == now.hour and reminder_local.date() == today:
task.write({'task_state': 'done'})
will_id = task.yuthon_will_task_id
will_id.task_state = 'done'
# 构建提醒消息内容
request_hour_from = dict(task.yuthon_will_task_id._fields['request_hour_from'].selection).get(
task.yuthon_will_task_id.request_hour_from, '')
_weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
_start = task.yuthon_will_task_id.start_date
_week_str = _weekdays[_start.weekday()] if _start else ''
data_date = '开始时间: ' + str(_start) + ' ' + _week_str + '--' + str(request_hour_from) + '\n'
data_name = "事项名称: " + task.yuthon_will_task_id.name + '\n'
data_user_ids = '参与人: ' + ', '.join(will_id.employee_ids.mapped('name')) + '\n'
data_nota = ('备注: ' + will_id.remarks + '\n') if will_id.remarks else '\n'
body = data_date + data_name + data_user_ids + data_nota
# 使用 Odoo 内部消息通知替代企业微信推送
will_id.message_post(
body=body,
subject="日程事项提醒",
message_type='notification',
subtype_xmlid='mail.mt_comment',
)
for employee_id in task.employee_ids:
url = f'https://phone.thtzjt.com/phone/Schedule/{task.yuthon_will_task_id.id}/{employee_id.user_id.id}'
request_hour_from = dict(task.yuthon_will_task_id._fields['request_hour_from'].selection).get(task.yuthon_will_task_id.request_hour_from, '') + '\n'
_weekdays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
_start = task.yuthon_will_task_id.start_date
_week_str = _weekdays[_start.weekday()] if _start else ''
data_date = '开始时间:' + str(_start) + ' ' + _week_str + '--' + str(request_hour_from) + '\n'
data_name = "事项名称: " + task.yuthon_will_task_id.name + '\n'
data_user_ids = '参与人: ' + ', '.join(will_id.employee_ids.mapped('name')) + '\n'
data_employee_id = ('司机:' + ', '.join(will_id.employee_ids2.mapped('name')) + '\n') if will_id.employee_ids2 else ''
data_nota = ('备注:' + will_id.remarks) if will_id.remarks else ('') + '\n'
data = data_date + data_name + data_user_ids + data_employee_id + data_nota
# url = f'https://phone.thtzjt.com/phone/Schedule/{will_id.id}/{employee_id.id}'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id=employee_id.wecom_userid, title="日程事项提醒", description=data,
url=url, btntxt="详细信息")
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id="18562027762", title="日程事项提醒", description=data,
url=url, btntxt="详细信息")
def button_add_comment(self):
"""手动发送日程提醒——通过 Odoo 内部消息通知"""
_weekdays2 = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
_week_str2 = _weekdays2[self.start_date.weekday()] if self.start_date else ''
_hour_from = dict(self._fields['request_hour_from'].selection).get(self.request_hour_from, '')
data_date = '开始时间: ' + str(self.start_date) + ' ' + _week_str2 + '--' + _hour_from + '\n'
data_name = "事项名称: " + self.name + '\n'
data_user_ids = '参与人: ' + ', '.join(self.employee_ids.mapped('name')) + '\n'
data_nota = ('备注: ' + self.remarks + '\n') if self.remarks else '\n'
body = data_date + data_name + data_user_ids + data_nota
# 使用 Odoo 内部消息通知替代企业微信推送
self.message_post(
body=body,
subject="日程事项提醒",
message_type='notification',
subtype_xmlid='mail.mt_comment',
)
for employee_id in self.employee_ids + self.employee_ids2:
_weekdays2 = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
_week_str2 = _weekdays2[self.start_date.weekday()] if self.start_date else ''
_hour_from = dict(self._fields['request_hour_from'].selection).get(self.request_hour_from, '')
data_date = '开始时间:' + str(self.start_date) + ' ' + _week_str2 + '--' + _hour_from + '\n'
data_name = "事项名称: " + self.name + '\n'
data_user_ids = '参与人: ' + ', '.join(self.employee_ids.mapped('name')) + '\n'
data_employee_id = ('司机:' + ', '.join(self.employee_ids2.mapped('name')) + '\n') if self.employee_ids2 else ''
data_nota = ('备注:' + self.remarks + '\n') if self.remarks else ('') + '\n'
data = data_date + data_name + data_user_ids + data_nota + data_employee_id
url = f'https://phone.thtzjt.com/phone/Schedule/{self.id}/{employee_id.user_id.id}'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id=employee_id.wecom_userid,
title="日程事项提醒", description=data, url=url, btntxt="详细信息")
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id="18562027762",
title="日程事项提醒", description=data, url=url, btntxt="详细信息")
# if test_msg_id:
# self.write({'msg_id': test_msg_id[-1]['msgid']})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
@ -307,16 +342,15 @@ class YuthonWillTask(models.Model):
}
def button_add_comment_cancel(self):
"""取消日程并发送内部通知"""
self.write({'task_state': 'cancel'})
body = "事项名称: " + self.name + '\t' + '该事项已取消'
# 使用 Odoo 内部消息通知替代企业微信推送
self.message_post(
body=body,
subject="日程事项取消提醒",
message_type='notification',
subtype_xmlid='mail.mt_comment',
)
employee_list = [i.id for i in self.employee_ids] + [i.id for i in self.employee_ids2]
for employee_id in self.env['hr.employee'].search([('id', 'in', employee_list)]):
if not employee_id.wecom_userid:
raise UserError("请同步{}的企业微信".format(employee_id.name))
url = f'https://oa.thtzjt.com/webh5#/journey?id={self.id}'
self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task', user_id=employee_id.wecom_userid,
title="日程事项提醒", description="事项名称: " + self.name + '\t' + '该事项已取消',
url=url, btntxt="详细信息")
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
@ -337,14 +371,13 @@ class YuthonWillTask(models.Model):
def write(self, vals):
"""修改日程时,先删除旧提醒任务,再创建新的"""
res = super(YuthonWillTask, self).write(vals)
key_fields = ['start_date', 'request_hour_from', 'task_time_ids', 'employee_ids', 'task_type']
key_fields = ['start_date', 'request_hour_from', 'task_time_ids', 'employee_ids', 'employee_ids2', 'task_type', 'room_booking_id']
if any(field in vals for field in key_fields):
self._sync_dates_task()
return res
def _sync_dates_task(self):
"""根据提醒时间配置同步创建日程提醒任务"""
reminder_employees = self.employee_ids | self.employee_id
reminder_employees = self.employee_ids | self.employee_ids2 | self.employee_id
if not reminder_employees:
return
old_tasks = self.env['yuthon.dates.task'].search([('yuthon_will_task_id', '=', self.id), ('task_state', '=', 'to_do')])
@ -353,7 +386,7 @@ class YuthonWillTask(models.Model):
start_hour = float(self.request_hour_from)
hours = int(start_hour)
minutes = int((start_hour - hours) * 60)
task_start_datetime = datetime.combine(self.start_date, time(hour=hours, minute=minutes))
task_start_datetime = datetime.combine(self.start_date,time(hour=hours, minute=minutes))
for task_time in self.task_time_ids:
reminder_time_str = task_time.name
reminder_hours = 0.0
@ -368,7 +401,7 @@ class YuthonWillTask(models.Model):
elif unit == '':
reminder_hours = num * 24.0
reminder_datetime = task_start_datetime - timedelta(hours=reminder_hours)
now = fields.Datetime.now() + timedelta(hours=8)
now = fields.datetime.now() + timedelta(hours=8)
if now > reminder_datetime:
continue
existing_dates_task = self.env['yuthon.dates.task'].search([
@ -386,8 +419,9 @@ class YuthonWillTask(models.Model):
task_vals['yuthon_will_task_id'] = self.id
self.env['yuthon.dates.task'].create(task_vals)
def schedule_reminders(self):
"""查看当前日程的提醒任务列表"""
return {
'type': 'ir.actions.act_window',
'name': '提醒任务详情',
@ -395,3 +429,4 @@ class YuthonWillTask(models.Model):
'res_model': 'yuthon.dates.task',
'domain': [('yuthon_will_task_id', '=', self.id)],
}

View File

@ -1,7 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_yuthon_task_time,yuthon_task_time,model_yuthon_task_time,base.group_user,1,1,1,0
access_yuthon_task_time_manager,yuthon_task_time_manager,model_yuthon_task_time,base.group_system,1,1,1,1
access_yuthon_will_task,yuthon_will_task,model_yuthon_will_task,base.group_user,1,1,1,0
access_yuthon_will_task_manager,yuthon_will_task_manager,model_yuthon_will_task,base.group_system,1,1,1,1
access_yuthon_dates_task,yuthon_dates_task,model_yuthon_dates_task,base.group_user,1,1,1,0
access_yuthon_dates_task_manager,yuthon_dates_task_manager,model_yuthon_dates_task,base.group_system,1,1,1,1
access_yuthon_task_time,yuthon_task_time,model_yuthon_task_time,base.group_user,1,1,1,1
access_yuthon_will_task,yuthon_will_task,model_yuthon_will_task,base.group_user,1,1,1,1
access_yuthon_dates_task,yuthon_dates_task,model_yuthon_dates_task,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_yuthon_task_time yuthon_task_time model_yuthon_task_time base.group_user 1 1 1 0 1
3 access_yuthon_task_time_manager access_yuthon_will_task yuthon_task_time_manager yuthon_will_task model_yuthon_task_time model_yuthon_will_task base.group_system base.group_user 1 1 1 1
4 access_yuthon_will_task access_yuthon_dates_task yuthon_will_task yuthon_dates_task model_yuthon_will_task model_yuthon_dates_task base.group_user 1 1 1 0 1
5
6
access_yuthon_dates_task_manager yuthon_dates_task_manager model_yuthon_dates_task base.group_system 1 1 1 1

View File

@ -13,5 +13,13 @@
<field name="category_id" ref="module_yuthon_will_task"/>
<field name="implied_ids" eval="[(4, ref('group_yuthon_will_task_user'))]"/>
</record>
<record id="group_module_room_booking" model="ir.module.category">
<field name="name">会议组</field>
</record>
<record id="group_room_booking_manager" model="res.groups">
<field name="name">会议管理员</field>
<field name="category_id" ref="group_module_room_booking"/>
<field name="users" eval="[(4, ref('base.user_admin'))]"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,105 @@
body, html {
margin: 0;
padding: 0;
height: 100%;
}
.o_main_content {
padding: 0 !important;
}
.task-detail-container {
width: 100%;
min-height: 100vh;
margin: 0;
padding: 10px;
box-sizing: border-box;
}
.main-title {
font-size: 24px;
font-weight: 600;
margin-bottom: 15px;
line-height: 1.4;
color: #000;
}
.right-align-value {
text-align: right;
}
.time-info {
color: #666;
font-size: 14px;
margin-bottom: 10px;
line-height: 1.5;
}
.address-info {
color: #666;
font-size: 14px;
margin-bottom: 25px;
line-height: 1.5;
}
.info-list {
border-top: 1px solid #eee;
padding-top: 10px;
}
.info-item {
display: flex;
padding: 12px 0;
border-bottom: 1px solid #eee;
flex-wrap: wrap;
}
.info-label {
width: 80px;
flex-shrink: 0;
color: #666;
font-size: 15px;
margin-bottom: 5px;
}
.info-value {
flex-grow: 1;
font-size: 15px;
color: #000;
line-height: 1.6;
word-wrap: break-word;
word-break: normal;
}
.description-item {
padding-bottom: 20px;
}
.description-content {
line-height: 1.6;
white-space: pre-line;
word-wrap: break-word;
}
.btn-back {
padding: 6px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-back:hover {
background-color: #0056b3;
}
@media (max-width: 576px) {
.info-label {
width: 70px;
}
.task-detail-container {
padding: 10px;
}
}

View File

@ -19,6 +19,18 @@ export class CustomTaskListController extends ListController {
context: {'task_state': 'will'},
});
}
openMeetForm() {
this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'room.booking',
name: '通知公告',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'current',
context: {},
});
}
}
registry.category("views").add("custom_task_tree_button", {

View File

@ -5,6 +5,9 @@
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="openTaskForm">
新建日程
</button>
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="openMeetForm">
新建会议
</button>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="task_detail_template" name="任务详情模板">
<t t-call="web.layout">
<t t-set="head">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<link rel="stylesheet" type="text/css"
href="/yuthon_will_task/static/src/css/task_detail.css" />
</t>
<div class="task-detail-container">
<h1 class="main-title">
<t t-esc="name" />
</h1>
<div class="time-info">
<t t-set="weekday_map"
t-value="{'0':'一','1':'二','2':'三','3':'四','4':'五','5':'六','6':'日'}" />
<t t-if="start_date and start_date != 'False'">
<span t-esc="start_date" />
<t t-set="date_obj"
t-value="datetime.datetime.strptime(start_date, '%Y-%m-%d')" />
<span>(星期<t t-esc="weekday_map[str(date_obj.weekday())]" /></span>
<t t-if="request_hour_from and request_hour_to">
<t t-set="from_time"
t-value="request_hour_from.replace('.5', ':30').replace('.0', ':00') if '.' in request_hour_from else request_hour_from + ':00'" />
<t t-set="to_time"
t-value="request_hour_to.replace('.5', ':30').replace('.0', ':00') if '.' in request_hour_to else request_hour_to + ':00'" />
<span> 时间:<t t-esc="from_time" /> - <t t-esc="to_time" /></span>
</t>
</t>
</div>
<div class="address-info"> 地址: <t t-esc="address" />
</div>
<div class="info-list">
<div class="info-item">
<div class="info-label">组织人</div>
<div class="info-value right-align-value">
<t t-esc="user_id" />
</div>
</div>
<div class="info-item">
<div class="info-label">参与人</div>
<div class="info-value right-align-value ">
<t t-esc="employee_ids" />
</div>
</div>
<div class="info-item description-item">
<div class="info-label">描述</div>
<div class="info-value ">
<t t-esc="description" />
</div>
</div>
<div class="info-item">
<div class="info-label">任务编号</div>
<div class="info-value right-align-value">
<t t-esc="code" />
</div>
</div>
</div>
</div>
</t>
</template>
</odoo>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_yuthon_dates_task_tree" model="ir.ui.view">
<field name="name">yuthon.dates.task.tree</field>
<record id="view_yuthon_dates_task_list" model="ir.ui.view">
<field name="name">yuthon.dates.task.list</field>
<field name="model">yuthon.dates.task</field>
<field name="arch" type="xml">
<tree string="日程任务" editable="bottom"
<list string="日程任务" editable="bottom"
decoration-success="task_state == 'to_do'"
decoration-danger="task_state == 'done'"
>
@ -12,7 +12,7 @@
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="reminder_time"/>
<field name="task_state"/>
</tree>
</list>
</field>
</record>
<record id="view_yuthon_dates_task_search" model="ir.ui.view">
@ -30,8 +30,8 @@
<record id="view_yuthon_dates_task_action" model="ir.actions.act_window">
<field name="name">日程任务</field>
<field name="res_model">yuthon.dates.task</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_yuthon_dates_task_tree"/>
<field name="view_mode">list</field>
<field name="view_id" ref="view_yuthon_dates_task_list"/>
<field name="search_view_id" ref="view_yuthon_dates_task_search"/>
<field name="context">{'search_default_task_state':1}</field>
</record>

View File

@ -4,11 +4,27 @@
name='会议日程'
web_icon="yuthon_will_task,static/description/icon.png"
sequence='10'/>
<!-- <menuitem id='menu_yuthon_will_task_root'-->
<!-- name='我的日程'-->
<!-- parent='menu_yuthon_will_task'-->
<!-- action='view_yuthon_will_task_action'-->
<!-- sequence='1'/>-->
<menuitem id='menu_yuthon_will_task_root2'
name='日程管理'
parent='menu_yuthon_will_task'
action='view_yuthon_will_task_action2'
sequence='10'/>
<menuitem id="room_menu_root_will"
name="预定会议"
action="room.room_booking_action"
parent="menu_yuthon_will_task"
groups="yuthon_will_task.group_room_booking_manager"
sequence="10"/>
<menuitem id="room_booking_menu_will"
name="会议室"
action="room.room_room_action"
parent="menu_yuthon_will_task"
sequence="12"/>
<menuitem id="task_type_menu"
name="配置"
parent="menu_yuthon_will_task"

View File

@ -1,21 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="yuthon_task_time_tree" model="ir.ui.view">
<field name="name">yuthon.task.time.tree</field>
<record id="yuthon_task_time_list" model="ir.ui.view">
<field name="name">yuthon.task.time.list</field>
<field name="model">yuthon.task.time</field>
<field name="arch" type="xml">
<tree string="提醒时长配置" editable="bottom">
<list string="提醒时长配置" editable="bottom">
<field name="sequence" widget="handle"/>
<field name="name" required="1"/>
<field name="number" required="1"/>
<field name="task_unit" required="1"/>
</tree>
</list>
</field>
</record>
<record id="yuthon_task_time_setting_action" model="ir.actions.act_window">
<field name="name">提醒时长配置</field>
<field name="res_model">yuthon.task.time</field>
<field name="view_mode">tree</field>
<field name="view_mode">list</field>
</record>
</odoo>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id='view_yuthon_will_task_tree' model='ir.ui.view'>
<field name='name'>yuthon.will.task.tree</field>
<record id='view_yuthon_will_task_list' model='ir.ui.view'>
<field name='name'>yuthon.will.task.list</field>
<field name='model'>yuthon.will.task</field>
<field name='arch' type='xml'>
<tree string='日程'
<list string='日程'
decoration-muted="task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
@ -17,14 +17,22 @@
<field name='employee_ids' widget="many2many_tags" required="1" options="{'no_create': True}"/>
<field name="address"/>
<field name='remarks'/>
<!-- <field name='document_ids' widget="many2many_tags"/>-->
<!-- <field name='employee_ids2' widget="many2many_tags" domain="[('user_role_ids.name', 'in', ['司机'])]" options="{'no_create': True}"/>-->
<!-- <field name='task_time_ids' widget="many2many_tags" required="1"/>-->
<field name='priority'/>
<field name='task_state' readonly="1"/>
<!-- <field name='users_ids' widget="many2many_tags" column_invisible="1"/>-->
<!-- <field name='user_role_ids' widget="many2many_tags" column_invisible="1"/>-->
<!-- <field name="employee_id" optional="hide"/>-->
<!-- <field name='creation_time' optional="hide"/>-->
<button name="button_add_comment" string="提醒" type="object" class="btn btn-blue"
invisible="task_state in ['done', 'cancel']"/>
<!-- <button name="back_wecom_msg" string="测试撤销" type="object" class="oe_highlight"/>-->
<button name="button_add_comment_cancel" string="取消" type="object" class="btn btn-blue"
invisible="task_state == 'cancel'"/>
<button name="schedule_reminders" string="提醒任务" type="object" class="btn btn-blue"/>
</tree>
</list>
</field>
</record>
<record id='yuthon_will_task_search' model='ir.ui.view'>
@ -111,6 +119,7 @@
<field name="employee_id"/>
<field name="task_state" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="is_company" invisible="1"/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"
domain="[('company_id', '=', company_id)] if is_this_company else []"/>
</group>
@ -121,23 +130,18 @@
</group>
<group>
<field name="remarks"/>
<field name="document_ids" widget="many2many_binary"/>
<field name="document_ids" widget="preview_many2many"/>
<field name="creation_time" invisible="1"/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" groups="base.group_user"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<record id='view_yuthon_will_task_tree2' model='ir.ui.view'>
<field name='name'>yuthon.will.task.tree2</field>
<record id='view_yuthon_will_task_list2' model='ir.ui.view'>
<field name='name'>yuthon.will.task.list2</field>
<field name='model'>yuthon.will.task</field>
<field name='arch' type='xml'>
<tree string='日程'
<list string='日程'
decoration-muted="task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
@ -150,13 +154,20 @@
<field name='employee_ids' widget="many2many_tags" required="1" options="{'no_create': True}"/>
<field name="address"/>
<field name='remarks'/>
<!-- <field name='document_ids' widget="many2many_tags"/>-->
<!-- <field name='employee_ids2' widget="many2many_tags" domain="[('user_role_ids.name', 'in', ['司机'])]" options="{'no_create': True}"/>-->
<!-- <field name='task_time_ids' widget="many2many_tags" required="1"/>-->
<field name='priority'/>
<field name='task_state' readonly="1"/>
<!-- <field name='users_ids' widget="many2many_tags" column_invisible="1"/>-->
<!-- <field name='user_role_ids' widget="many2many_tags" column_invisible="1"/>-->
<!-- <field name="employee_id" optional="hide"/>-->
<!-- <field name='creation_time' optional="hide"/>-->
<button name="button_add_comment" string="提醒" type="object" class="btn btn-blue"
invisible="task_state in ['done', 'cancel']"/>
<button name="button_add_comment_cancel" string="取消" type="object" class="btn btn-blue"
invisible="task_state == 'cancel'"/>
</tree>
</list>
</field>
</record>
<record id="view_yuthon_will_task_form2" model="ir.ui.view">
@ -239,15 +250,15 @@
</group>
<group>
<field name="remarks"/>
<field name="document_ids" widget="many2many_binary"/>
<field name="document_ids" widget="preview_many2many"/>
<field name="creation_time" invisible="1"/>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" groups="base.group_user"/>
<field name="activity_ids"/>
<field name="message_ids"/>
</div>
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids" groups="base.group_user"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
</form>
</field>
</record>
@ -297,7 +308,7 @@
参与人 <field name="employee_ids" widget="many2many_tags"/><t t-esc="record.employee_ids.raw_value.length"/>
</div>
</t>
<!-- 地点 -->
<!-- 会议室 -->
<t t-if="record.address.raw_value">
<div style="color: #555; font-size: 13px; margin-bottom: 4px;">
地点 <field name="address"/>
@ -335,22 +346,22 @@
<record id='view_yuthon_will_task_action' model='ir.actions.act_window'>
<field name='name'>我的日程</field>
<field name='res_model'>yuthon.will.task</field>
<field name='view_mode'>tree,kanban,calendar,form</field>
<field name='view_mode'>list,kanban,calendar,form</field>
<field name='domain'>[('users_ids', 'in', [uid]), ('task_state', '!=', 'cancel')]</field>
<field name="context">{'search_default_next_seven_days': 1}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_tree2')}),
(0, 0, {'view_mode': 'list', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_list2')}),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_kanban')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_form2')})]" />
</record>
<record id='view_yuthon_will_task_action2' model='ir.actions.act_window'>
<field name='name'>日程管理</field>
<field name='res_model'>yuthon.will.task</field>
<field name='view_mode'>tree,kanban,calendar,form</field>
<field name='domain'>[('task_state', '!=', 'cancel'), ('is_this_company','=',True)]</field>
<field name='view_mode'>list,kanban,calendar,form</field>
<field name='domain'>[('task_state', '!=', 'cancel'), ('is_company','=',True)]</field>
<field name="context">{'search_default_next_seven_days': 1}</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_tree2')}),
(0, 0, {'view_mode': 'list', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_list2')}),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_kanban')})]" />
</record>
<record id="all_yuthon_will_task" model="ir.actions.server">