p{FQ6L=Ku%o~QCuLeayVrtM5p0{)=Zm9WGI^x!3sxjODov7DpJ@WoC%K5>Yj6{
zMD&w6qj5lo`|j1bEL8|$TGKqe<36JF?HY5~0!TJmPV%+RdIbp;It
z3?~9EPNb!6`MN?VAkSBx7Q(CtL@qqAkjyVh7^JN6tsap^jsp$J0cuOvThdo8jFAge
zn{!+-;`_xPy<7IK>GC6e@a3Z3!ZBbkcd3nFD?7h`yg&9I7IysBS}DjPkiT7~(j#Kr
zKsh&SY}=T9MwHBZ;wEXzqKax~Y1LIjvJoR1SMGBlZtNzP;VH%~82UKEX%2O-7zs6Y
zIN~*@x8-8Vl3Zk(Au9hA?Bo_b*wuzh`?v(^PB$Vcv!loAFo8wiTC4cPYl*{j^hVcs
zRryq!`uucE3+qSZ+2^?V64;#-#!M-$ieN!>22jVr%XzOR^zO8jVs8eM<>Yzg)C>&!6a(>{vs%R%{xgZ$M=eY-wlLQ7q
z{#jl9HNN}OQUGHC0R!O!?p&1rX}4bl{(Db7r>|evbMQ4I`JK%ET=Zo+d&c>-X!t)k
zKlj)FTI?4KFf1%UlK+W#-SXXszo^RHUcf8+ff4gO^JWuAD({>AR*e_;QaHD2ob
zW$gV;Ymw~#0UDWG9^AQ^h-;(|3>>i668xdFIzmn>DZ(Gcf;o;g_m{T-xM+d
zx%#t)DjQh)K2!MU_*TR3{{+{2HlLY@6$T5I0fQSIU
JwyB=+{yzkj+n4|V
literal 0
HcmV?d00001
diff --git a/yuthon_hr_employee/static/scss/yuthon_employee_scss.scss b/yuthon_hr_employee/static/scss/yuthon_employee_scss.scss
new file mode 100644
index 0000000..c35fa00
--- /dev/null
+++ b/yuthon_hr_employee/static/scss/yuthon_employee_scss.scss
@@ -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;
+}
\ No newline at end of file
diff --git a/yuthon_hr_employee/views/inherit_hr_job_views.xml b/yuthon_hr_employee/views/inherit_hr_job_views.xml
new file mode 100644
index 0000000..b0fcda3
--- /dev/null
+++ b/yuthon_hr_employee/views/inherit_hr_job_views.xml
@@ -0,0 +1,206 @@
+
+
+
+ hr.job.inherit
+ hr.job
+
+
+
+ 20
+
+
+ 岗位
+
+
+ compiling_status == '1'
+
+
+ 所属部门
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.job.inherit.form
+ hr.job
+
+
+
+ 基础信息
+
+
+ 任职要求
+
+
+ 其他信息
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 所属部门
+ 1
+
+
+
+ 1
+
+
+
+ 1
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+ 1
+
+
+
+
+
+
+
+ 及以上
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 至
+
+
+
+
+
+
+ 年及以上
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ custom.hr.job.form.inherit
+ hr.job
+
+
+
+ 1
+
+
+
+
+
+ hr.job.form1
+ hr.job
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+
diff --git a/yuthon_hr_employee/views/inherit_hr_work_location_views.xml b/yuthon_hr_employee/views/inherit_hr_work_location_views.xml
new file mode 100644
index 0000000..3db4d27
--- /dev/null
+++ b/yuthon_hr_employee/views/inherit_hr_work_location_views.xml
@@ -0,0 +1,17 @@
+
+
+
+ hr.work.location.inherit.form
+ hr.work.location
+
+
+
+ 0
+
+
+ 联系人
+
+
+
+
+
diff --git a/yuthon_hr_employee/views/inherit_res_groups_views.xml b/yuthon_hr_employee/views/inherit_res_groups_views.xml
new file mode 100644
index 0000000..e6bc12d
--- /dev/null
+++ b/yuthon_hr_employee/views/inherit_res_groups_views.xml
@@ -0,0 +1,28 @@
+
+
+
+ unlink.res.groups.form
+ res.groups
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+ many2many_checkboxes
+
+
+
+
diff --git a/yuthon_hr_employee/views/inherit_res_user_views.xml b/yuthon_hr_employee/views/inherit_res_user_views.xml
new file mode 100644
index 0000000..4e499e6
--- /dev/null
+++ b/yuthon_hr_employee/views/inherit_res_user_views.xml
@@ -0,0 +1,29 @@
+
+
+
+ 批量创建员工
+
+
+ code
+ records.all_create_users()
+
+
+
+ 更新邮件信息
+
+
+ code
+ records.all_users_email()
+
+
+
+ res.users.tree.inherit
+ res.users
+
+
+
+ 1
+
+
+
+
\ No newline at end of file
diff --git a/yuthon_hr_employee/views/yuthon_hr_employee_views.xml b/yuthon_hr_employee/views/yuthon_hr_employee_views.xml
new file mode 100644
index 0000000..08c97cc
--- /dev/null
+++ b/yuthon_hr_employee/views/yuthon_hr_employee_views.xml
@@ -0,0 +1,524 @@
+
+
+
+ hr.employee.basic.personal.information
+ hr.employee
+
+
+
+
+
+ hr.employee.inherit.del.form.inherit
+ hr.employee
+
+
+
+
+ 0
+
+
+ 上级领导
+ "{'no_create': True}"
+
+
+ 1
+
+
+ 1
+ "{'no_open': True}"
+
+
+ 1
+
+
+ "{'no_open': True}"
+
+
+ "{'no_open': True}"
+
+
+ "{'no_open': True}"
+
+
+
+
+
+
+ ztree_select
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ "{'no_open': True}"
+
+
+ "{'no_create': True, 'no_open': True}"
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.employee.inherit.del.tree
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.employee.inherit.del.form
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+ hr.employee.contacts.search
+ hr.employee
+
+
+
+
+
+
+
+ hr.employee.search
+ hr.employee
+
+
+
+
+
+
+
+
+
+ hr.employee.member
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ hr.employee.member
+ hr.employee
+
+
+
+
+
+
+
+
+
+ hr.employee.inherit.search
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 批量更新员工
+
+
+ code
+ action = model.action_hr_employee()
+
+
+
+ list,kanban,form,activity,graph,pivot
+
+
+
+ 通讯录
+ hr.employee
+ [('is_company', '=', True)]
+ list,kanban,form,search
+
+
+
+
+ 个人基础信息
+
+
+ code
+
+ action = model.get_my_employee_action()
+
+
+
+
+ 重置密码
+
+
+ code
+
+ action = model.change_password_wizard()
+
+
+
+ 会员列表
+ hr.employee
+ [('is_membership', '=', True), ('is_company', '=', True)]
+ list,kanban,form,search
+
+
+
+
+ list,kanban,form,activity,graph,pivot
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yuthon_hr_employee/views/yuthon_inherit_hr_employee.xml b/yuthon_hr_employee/views/yuthon_inherit_hr_employee.xml
new file mode 100644
index 0000000..0bd210f
--- /dev/null
+++ b/yuthon_hr_employee/views/yuthon_inherit_hr_employee.xml
@@ -0,0 +1,131 @@
+
+
+
+ yuthon.inherit.hr.employee
+ hr.employee
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/yuthon_hr_employee/wizard/__init__.py b/yuthon_hr_employee/wizard/__init__.py
new file mode 100644
index 0000000..6cca9d4
--- /dev/null
+++ b/yuthon_hr_employee/wizard/__init__.py
@@ -0,0 +1,3 @@
+from . import hr_employee_wizard
+from . import hr_employee_time_wizard
+from . import reset_password_wizard
diff --git a/yuthon_hr_employee/wizard/hr_employee_time_wizard.py b/yuthon_hr_employee/wizard/hr_employee_time_wizard.py
new file mode 100644
index 0000000..acde4d9
--- /dev/null
+++ b/yuthon_hr_employee/wizard/hr_employee_time_wizard.py
@@ -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})
\ No newline at end of file
diff --git a/yuthon_hr_employee/wizard/hr_employee_time_wizard_views.xml b/yuthon_hr_employee/wizard/hr_employee_time_wizard_views.xml
new file mode 100644
index 0000000..93cbe6b
--- /dev/null
+++ b/yuthon_hr_employee/wizard/hr_employee_time_wizard_views.xml
@@ -0,0 +1,22 @@
+
+
+
+ 考勤时间表
+ yuthon.hr.employee.time.wizard
+
+
+
+
+
\ No newline at end of file
diff --git a/yuthon_hr_employee/wizard/hr_employee_wizard.py b/yuthon_hr_employee/wizard/hr_employee_wizard.py
new file mode 100644
index 0000000..f8b815d
--- /dev/null
+++ b/yuthon_hr_employee/wizard/hr_employee_wizard.py
@@ -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)
+
+
+
+
diff --git a/yuthon_hr_employee/wizard/hr_employee_wizard_views.xml b/yuthon_hr_employee/wizard/hr_employee_wizard_views.xml
new file mode 100644
index 0000000..0f48f3c
--- /dev/null
+++ b/yuthon_hr_employee/wizard/hr_employee_wizard_views.xml
@@ -0,0 +1,34 @@
+
+
+
+ 员工模板 下载
+ self
+ /yuthon_hr_employee/static/others/user_template.xlsx
+
+
+ 审批意见弹窗
+ yuthon.hr.employee.wizard
+
+
+
+
+
\ No newline at end of file
diff --git a/yuthon_hr_employee/wizard/reset_password_wizard.py b/yuthon_hr_employee/wizard/reset_password_wizard.py
new file mode 100644
index 0000000..cf99c63
--- /dev/null
+++ b/yuthon_hr_employee/wizard/reset_password_wizard.py
@@ -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
+
diff --git a/yuthon_hr_employee/wizard/reset_password_wizard_viwes.xml b/yuthon_hr_employee/wizard/reset_password_wizard_viwes.xml
new file mode 100644
index 0000000..53c0919
--- /dev/null
+++ b/yuthon_hr_employee/wizard/reset_password_wizard_viwes.xml
@@ -0,0 +1,21 @@
+
+
+
+ reset.password.wizard.form
+ reset.password.wizard
+
+
+
+
+
\ No newline at end of file
diff --git a/yuthon_notice/__init__.py b/yuthon_notice/__init__.py
index 724cd13..f5ba686 100644
--- a/yuthon_notice/__init__.py
+++ b/yuthon_notice/__init__.py
@@ -1,3 +1,2 @@
# -*- coding: utf-8 -*-
-from . import models
-from . import controllers
\ No newline at end of file
+from . import models
\ No newline at end of file
diff --git a/yuthon_notice/__manifest__.py b/yuthon_notice/__manifest__.py
index fd3f9e7..e1432bb 100644
--- a/yuthon_notice/__manifest__.py
+++ b/yuthon_notice/__manifest__.py
@@ -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': {
@@ -30,4 +30,5 @@
},
'installable': True,
'application': True,
-}
\ No newline at end of file
+}
+
diff --git a/yuthon_notice/models/__init__.py b/yuthon_notice/models/__init__.py
index b3a43c5..9bc41f1 100644
--- a/yuthon_notice/models/__init__.py
+++ b/yuthon_notice/models/__init__.py
@@ -3,4 +3,5 @@
from . import yuthon_notice
from . import yuthon_notice_type
from . import yuthon_confirm_users
+from . import hr_department
diff --git a/yuthon_notice/models/hr_department.py b/yuthon_notice/models/hr_department.py
new file mode 100644
index 0000000..021c944
--- /dev/null
+++ b/yuthon_notice/models/hr_department.py
@@ -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()
diff --git a/yuthon_notice/models/yuthon_confirm_users.py b/yuthon_notice/models/yuthon_confirm_users.py
index a1e013c..747485e 100644
--- a/yuthon_notice/models/yuthon_confirm_users.py
+++ b/yuthon_notice/models/yuthon_confirm_users.py
@@ -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="详细信息")
\ No newline at end of file
diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py
index 46cfa01..a7eaed0 100644
--- a/yuthon_notice/models/yuthon_notice.py
+++ b/yuthon_notice/models/yuthon_notice.py
@@ -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
-
-
-
-
-# 工作流的指定申请人逻辑?
-# 转交的时候选择条件,进行优化
-
-# 报表合同和产品业务
-# 企业微信等三方平台框架--和工作流
\ No newline at end of file
diff --git a/yuthon_notice/report/notice_print_report.xml b/yuthon_notice/report/notice_print_report.xml
new file mode 100644
index 0000000..8753674
--- /dev/null
+++ b/yuthon_notice/report/notice_print_report.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ Notice Small Top Margin
+ A4
+ 5
+ Portrait
+
+
+
+ 通知公告
+
+ yuthon.notice
+ qweb-pdf
+ yuthon_notice.notice_print_template
+ yuthon_notice.notice_print_template
+
+ report
+ '通知公告_%s_%s' % (object.name or '', object.code or '')
+
+
+
diff --git a/yuthon_notice/report/notice_print_template.xml b/yuthon_notice/report/notice_print_template.xml
new file mode 100644
index 0000000..75cce6a
--- /dev/null
+++ b/yuthon_notice/report/notice_print_template.xml
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+
+ 流水号:
+
+
+
+
+ 通知公告
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+ 普件
+ 急件
+ 特急件
+ |
+
+
+
+ |
+
+ |
+
+
+
+ |
+
+ |
+
+
+
+ |
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+ | 发布范围 |
+
+
+
+ |
+
+
+ 、
+
+ |
+
+
+
+ |
+
+
+ 、
+
+ |
+
+
+
+ |
+
+
+ 、
+
+ |
+
+
+
+ |
+
+
+ 、
+
+ |
+
+
+
+
+ | 审批详情 |
+
+
+
+
+ |
+ |
+
+
+
+
+ |
+
+
+
+
+
+
+
+
diff --git a/yuthon_notice/static/scss/notice_scss.scss b/yuthon_notice/static/scss/notice_scss.scss
index 0bcc2b2..cd056d7 100644
--- a/yuthon_notice/static/scss/notice_scss.scss
+++ b/yuthon_notice/static/scss/notice_scss.scss
@@ -2,4 +2,8 @@
.list_ids_width{
width: 80px !important;
max-width: 80px !important;
+}
+
+.btn-blue {
+ color: #0069ff !important;
}
\ No newline at end of file
diff --git a/yuthon_notice/views/yuthon_confirm_users_views.xml b/yuthon_notice/views/yuthon_confirm_users_views.xml
index 6cc06e4..53a3f7f 100644
--- a/yuthon_notice/views/yuthon_confirm_users_views.xml
+++ b/yuthon_notice/views/yuthon_confirm_users_views.xml
@@ -1,18 +1,26 @@
-
- yuthon.confirm.users.tree
+
+ yuthon.confirm.users.list
yuthon.confirm.users
-
+
-
+
+
+
+
+
+
+
+
@@ -32,7 +40,7 @@
公告确认信息
yuthon.confirm.users
- list
+ tree
-
\ No newline at end of file
+
diff --git a/yuthon_notice/views/yuthon_cs_tree_views.xml b/yuthon_notice/views/yuthon_cs_tree_views.xml
index eec7a0e..098445b 100644
--- a/yuthon_notice/views/yuthon_cs_tree_views.xml
+++ b/yuthon_notice/views/yuthon_cs_tree_views.xml
@@ -9,9 +9,9 @@
-
- Yuthon CS Tree
- yuthon.cs.tree
+
+ Yuthon CS list
+ yuthon.cs.list
list
\ No newline at end of file
diff --git a/yuthon_notice/views/yuthon_notice_type_views.xml b/yuthon_notice/views/yuthon_notice_type_views.xml
index 5e0b009..7126dba 100644
--- a/yuthon_notice/views/yuthon_notice_type_views.xml
+++ b/yuthon_notice/views/yuthon_notice_type_views.xml
@@ -1,7 +1,7 @@
-
- yuthon.notice.type.tree
+
+ yuthon.notice.type.list
yuthon.notice.type
diff --git a/yuthon_notice/views/yuthon_notice_views.xml b/yuthon_notice/views/yuthon_notice_views.xml
index b206c5b..1ab262f 100644
--- a/yuthon_notice/views/yuthon_notice_views.xml
+++ b/yuthon_notice/views/yuthon_notice_views.xml
@@ -1,24 +1,24 @@
-
- yuthon.notice.tree
+
+ yuthon.notice.list
yuthon.notice
+ decoration-muted="notice_state == 'stop'" default_order="create_date1 desc">
-
-
+
+
-
-
-
-
-
+
+
+
+
+
@@ -29,13 +29,10 @@
yuthon.notice
-
- yuthon.notice.tree
+
+ yuthon.notice.list
yuthon.notice
+ js_class="custom_notice_tree_button" default_order="create_date1 desc">
-
+
@@ -135,7 +129,7 @@
-
+
@@ -153,8 +147,8 @@
-
-
+
+
@@ -166,9 +160,8 @@
-
-
-
+
+
@@ -183,18 +176,19 @@
通知公告
+ [('users_ids','in',uid),('notice_state','=','yes')]
yuthon.notice
list,form
- [('notice_state', '=', 'yes'),('users_ids', 'in', [uid])]
通知公告管理
+ [('is_company', '=', True)]
yuthon.notice
- list,form
+ tree,form
-
+
\ No newline at end of file
diff --git a/yuthon_will_task/models/__init__.py b/yuthon_will_task/models/__init__.py
index 55d1529..93c6a21 100644
--- a/yuthon_will_task/models/__init__.py
+++ b/yuthon_will_task/models/__init__.py
@@ -1,3 +1,4 @@
from . import yuthon_will_task
from . import yuthon_task_time
+from . import inherit_room_booking
from . import yuthon_dates_task
diff --git a/yuthon_will_task/models/inherit_room_booking.py b/yuthon_will_task/models/inherit_room_booking.py
new file mode 100644
index 0000000..514e33e
--- /dev/null
+++ b/yuthon_will_task/models/inherit_room_booking.py
@@ -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
diff --git a/yuthon_will_task/models/yuthon_dates_task.py b/yuthon_will_task/models/yuthon_dates_task.py
index 1d81bf1..fd722be 100644
--- a/yuthon_will_task/models/yuthon_dates_task.py
+++ b/yuthon_will_task/models/yuthon_dates_task.py
@@ -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='状态')
+
diff --git a/yuthon_will_task/models/yuthon_task_time.py b/yuthon_will_task/models/yuthon_task_time.py
index bff4c9b..359794f 100644
--- a/yuthon_will_task/models/yuthon_task_time.py
+++ b/yuthon_will_task/models/yuthon_task_time.py
@@ -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:
diff --git a/yuthon_will_task/models/yuthon_will_task.py b/yuthon_will_task/models/yuthon_will_task.py
index f7dc061..32ab9dc 100644
--- a/yuthon_will_task/models/yuthon_will_task.py
+++ b/yuthon_will_task/models/yuthon_will_task.py
@@ -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):
"""结束时间默认当前小时+1(23+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 HH∶MM" 格式
+ 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)],
}
+
diff --git a/yuthon_will_task/security/ir.model.access.csv b/yuthon_will_task/security/ir.model.access.csv
index cd07842..2de527a 100644
--- a/yuthon_will_task/security/ir.model.access.csv
+++ b/yuthon_will_task/security/ir.model.access.csv
@@ -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
+
+
diff --git a/yuthon_will_task/security/yuthon_will_task_security.xml b/yuthon_will_task/security/yuthon_will_task_security.xml
index 7007475..0056a27 100644
--- a/yuthon_will_task/security/yuthon_will_task_security.xml
+++ b/yuthon_will_task/security/yuthon_will_task_security.xml
@@ -13,5 +13,13 @@
+
+ 会议组
+
+
+ 会议管理员
+
+
+
-
+
\ No newline at end of file
diff --git a/yuthon_will_task/static/src/css/task_detail.css b/yuthon_will_task/static/src/css/task_detail.css
new file mode 100644
index 0000000..2277e8c
--- /dev/null
+++ b/yuthon_will_task/static/src/css/task_detail.css
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/yuthon_will_task/static/src/js/task_create_button.js b/yuthon_will_task/static/src/js/task_create_button.js
index 44b8cc2..ad37e8f 100644
--- a/yuthon_will_task/static/src/js/task_create_button.js
+++ b/yuthon_will_task/static/src/js/task_create_button.js
@@ -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", {
diff --git a/yuthon_will_task/static/src/xml/task_create_button.xml b/yuthon_will_task/static/src/xml/task_create_button.xml
index 973244d..8ec906c 100644
--- a/yuthon_will_task/static/src/xml/task_create_button.xml
+++ b/yuthon_will_task/static/src/xml/task_create_button.xml
@@ -5,6 +5,9 @@
+