This commit is contained in:
李鹏宇 2026-06-26 09:40:27 +08:00
commit 782bea26af
42 changed files with 2187 additions and 0 deletions

View File

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

View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
{
'name': "Yuthon notice",
'summary': "通知公告",
'description': """
""",
'author': 'pengyuthon',
'website': 'https://www.pengyuthon.com',
'category': 'Human Resources/Attendances',
'version': '0.1',
'license': 'LGPL-3',
'depends': ['base', 'hr', 'mail'],
'data': [
'security/ir.model.access.csv',
'data/notice_code_data.xml',
'data/notice_cron_data.xml',
'data/notice_type_data.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': {
'web.assets_backend': [
'yuthon_notice/static/src/js/custom_create_button.js',
'yuthon_notice/static/src/xml/custom_create_button.xml',
'yuthon_notice/static/scss/notice_scss.scss',
],
},
'installable': True,
'application': True,
}

View File

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

View File

@ -0,0 +1,133 @@
import base64
import json
from odoo import http, fields
from odoo.http import request
from urllib.parse import quote
class YuthonNotice(http.Controller):
@http.route('/yuthon/yuthon/notice', methods=['POST'], type='http', auth='none', csrf=False)
def index(self, **kwargs):
id = kwargs.get('id')
data = request.env['yuthon.notice'].sudo().search([("id", "=", int(id))])
documents = []
for document in data.documents_ids:
documents.append({
'id': document.id,
'name': document.name,
'url': document.url,
'type': document.type,
'file_size': document.file_size,
'file_size_display':document.file_size_display,
'mimetype': document.mimetype,
'datas': document.datas.decode('utf-8') if document.datas else None,
})
result = {
'id': data.id,
'title': data.name,
'code': data.code,
'create_date1': fields.Date.to_string(data.create_date1),
'notice_text': data.notice_text,
'documents':documents,
'user_name':data.users_id.name,
'read_number': data.read_number,
}
return json.dumps({
'data':result,
'code':200,
'message':'success'
})
@http.route('/yuthon/notice/list', methods=['POST'], type='http', auth='none', csrf=False)
def yuthon_notice_list(self, **kwargs):
user_id = kwargs.get('users_id') # 按创建人筛选
domain = []
if user_id:
user_id_int = int(user_id)
domain.append(('users_id', '=', user_id_int))
notice_data = request.env['yuthon.notice'].sudo().search(domain)
result_list = []
urgency_map = {'normal': '普件', 'urgent': '急件', 'emergency': '特急件'}
for notice in notice_data:
department_list = []
file_list = []
for attach in notice.documents_ids:
file_url = 'http://view.officeapps.live.com/op/view.aspx?src=' + quote('https://oa.thtzjt.com' + '/web/content/office_preview/' + str(attach.id))
file_list.append({'file_url': file_url, 'file_name': attach.display_name})
result = {
'id': notice.id,
'title': notice.name,
'code': notice.code,
'notice_text': notice.notice_text,
'create_date1': fields.Date.to_string(notice.create_date1),
'user_name': notice.users_id.name,
'read_number': notice.read_number or 0,
'department_ids': department_list,
'file_list': file_list,
'urgency_level': notice.urgency_level,
'urgency_name': urgency_map.get(notice.urgency_level, ''),
}
result_list.append(result)
return json.dumps({
'data': result_list,
'code': 200,
'message': 'success'
})
@http.route('/yuthon/notice/record', methods=['POST'], type='http', auth='none', csrf=False)
def yuthon_notice_record(self, **kwargs):
id = kwargs.get('id')
data = request.env['yuthon.notice'].sudo().search([("id", "=", int(id))])
department_list = []
for dept in data.department_ids:
department_list.append({
'id': dept.id,
'name': dept.name,
})
role_group_list = [
{'id': group.id, 'name': group.name} # res.groups的name字段是角色组名称
for group in data.users_role_group_ids
]
file_list = []
for attach in data.documents_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,
})
urgency_map = {'normal': '普件','urgent': '急件','emergency': '特急件',}
urgency_name = urgency_map.get(data.urgency_level)
result = {
'id': data.id,
'title': data.name,
'code': data.code,
'notice_text': data.notice_text,
'create_date1': fields.Date.to_string(data.create_date1),
'user_name': data.users_id.name,
'read_number': data.read_number,
'department_ids': department_list,
'users_role_group_ids': role_group_list,
'file_list': file_list,
'urgency_name': urgency_name,
}
return json.dumps({
'data':result,
'code':200,
'message':'success'
})

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="notice_code_sequence" model="ir.sequence">
<field name="name">通知公告编号</field>
<field name="code">notice_code</field>
<field name="prefix">TZ%(y)s%(month)s%(day)s</field>
<field name="padding">3</field>
<field name="company_id" eval="False"/>
</record>
<template id="message_yuthon_confirm_users_data">
<p style="margin: 0px;">
<span>尊敬的用户,</span><br />
<span style="margin-top: 8px;">您有一条新的公告确认请求
<t t-esc="object.name"/> 请登录系统查看详情。
</span>
</p>
<p style="margin-top: 24px; margin-bottom: 16px;">
<a t-att-href="access_link" t-att-data-oe-model="object._name" t-att-data-oe-id="object.id" style="background-color:#2C65F7; padding: 10px; text-decoration: none; color: #fff; border-radius: 5px;">
查看 <t t-esc="model_description or 'document'"/>
</a>
</p>
</template>
</odoo>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<record forcecreate="True" id="ir_cron_yuthon_confirm_users" model="ir.cron">
<field name="name">公告未确认通知</field>
<field name="model_id" ref="model_yuthon_confirm_users"/>
<field name="state">code</field>
<field name="code">model.sync_confirm_users_notices()</field>
<field eval="False" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="yuthon_notice_type1" model="yuthon.notice.type"><field name="name">通知</field></record>
<record id="yuthon_notice_type2" model="yuthon.notice.type"><field name="name">公告</field></record>
<record id="yuthon_notice_type3" model="yuthon.notice.type"><field name="name">决定</field></record>
<record id="yuthon_notice_type4" model="yuthon.notice.type"><field name="name">其他</field></record>
</odoo>

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from . import yuthon_notice
from . import yuthon_notice_type
from . import yuthon_confirm_users

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools
from odoo.exceptions import UserError, ValidationError
class YuthonConfirmUsers(models.Model):
_name = "yuthon.confirm.users"
_description = "公告确认信息"
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="状态")
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="详细信息")
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')))
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=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')

View File

@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools, _
from odoo.exceptions import ValidationError, UserError
import logging
_logger = logging.getLogger(__name__)
class YuthonNotice(models.Model):
_name = "yuthon.notice"
_description = '通知公告'
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin']
_rec_name = 'name'
_order = "sequence desc"
name = fields.Char(string='标题')
code = fields.Char(string="编号", default=lambda self: self.env['ir.sequence'].next_by_code('notice_code'))
users_id = fields.Many2one('res.users', string="发布人", default=lambda self: self.env.user)
company_id = fields.Many2one(related="users_id.employee_id.company_id", string="公司", store=True)
department_id = fields.Many2one(related='users_id.employee_id.department_id', string="部门")
notice_type_id = fields.Many2one('yuthon.notice.type', string="类型")
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)
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="正文")
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="审核人")
approval_date = fields.Datetime(string="审核日期")
is_sticky = fields.Boolean(string="是否置顶")
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',
relation='yuthon_notice_users_rel',
column1='notice_id',
column2='user_id',
string='用户'
)
self_users_ids = fields.Many2many(
'res.users',
relation='yuthon_notice_current_users_rel',
column1='notice_id',
column2='user_id',
string="当前经办人",
store=True,
)
@api.model_create_multi
def create(self, vals_list):
for i in vals_list:
if i['department_ids'] == i['employee_ids'] == i['users_role_ids'] == i['users_role_group_ids']:
raise UserError('请填写发布的范围')
return super().create(vals_list)
def _get_default_name_title(self):
return self._description if self._description else self._name
name_title = fields.Char(default=_get_default_name_title)
@api.constrains('employee_ids', 'department_ids', 'users_role_ids', 'users_role_group_ids')
def _constrains_employee_related_fields(self):
def get_department_employees(department_id):
valid_user_ids = set()
employee_ids = self.env['hr.employee'].search([('department_id', '=', department_id.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:
if notice.env.context.get('skip_constrains'):
continue
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
all_users = set(
notice.employee_ids.mapped('user_id').ids +
group_role_ids.mapped('users').ids +
notice.users_role_ids.mapped('users').ids +
list(department_user_ids)
)
notice.with_context(skip_constrains=True).users_ids = [(6, 0, list(all_users))]
def get_no_read_count(self):
return self.search_count([('is_notice_roger', '=', 'no'), ('notice_state', '=', 'yes')])
def read(self, fields=None, load='_classic_read'):
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)
@api.depends('read_number')
def _compute_read_number(self):
notice_confirm_ids = self.env['yuthon.confirm.users'].search(
[('notice_id.id', '=', self.id), ('state', '=', 'yes')])
for read in self:
if notice_confirm_ids:
read.read_number = len(notice_confirm_ids)
else:
read.read_number = 0
def on_sequence(self):
self.sequence = 1
@api.onchange('is_sticky')
def _compute_sequence(self):
for i in self:
if i.is_sticky:
i.sequence = 1
else:
i.sequence = 0
def no_sequence(self):
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('该公告不允许通知,请勾选通知')
def look_up_notice_users(self):
return {
"type": "ir.actions.act_window",
"name": "通知公告确认信息",
"domain": [("notice_id", "=", self.id)],
"res_model": "yuthon.confirm.users",
"view_mode": "tree",
"context": {
"search_default_group_state": True,
},
}
def stop_notice(self):
self.write({
'notice_state': 'stop',
'end_date': fields.Date.today(),
})
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,11 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools
from odoo.exceptions import ValidationError, UserError
class YuthonNoticeType(models.Model):
_name = "yuthon.notice.type"
_description = "通知公告类型管理"
name = fields.Char(string="类型名称")
note = fields.Char(string="说明")

View File

@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_yuthon_notice,yuthon_notice,model_yuthon_notice,,1,1,1,1
access_yuthon_notice_type,yuthon_notice_type,model_yuthon_notice_type,base.group_user,1,1,1,1
access_yuthon_confirm_users,yuthon_confirm_users,model_yuthon_confirm_users,base.group_user,1,1,1,1
access_yuthon_cs_tree,yuthon_cs_tree,model_yuthon_cs_tree,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_notice yuthon_notice model_yuthon_notice 1 1 1 1
3 access_yuthon_notice_type yuthon_notice_type model_yuthon_notice_type base.group_user 1 1 1 1
4 access_yuthon_confirm_users yuthon_confirm_users model_yuthon_confirm_users base.group_user 1 1 1 1
5 access_yuthon_cs_tree yuthon_cs_tree model_yuthon_cs_tree base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,5 @@
.list_ids_width{
width: 80px !important;
max-width: 80px !important;
}

View File

@ -0,0 +1,29 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomNoticeListController extends ListController {
setup() {
super.setup();
}
openNoticeForm() {
this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'yuthon.notice',
name: '创建通知公告',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']], // 让 Odoo 找到默认的 form 视图
target: 'current', // 在当前窗口打开
context: {},
});
}
}
registry.category("views").add("custom_notice_tree_button", {
...listView,
Controller: CustomNoticeListController,
buttonTemplate: "yuthon_notice.ListView.Buttons",
});

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="yuthon_notice.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="openNoticeForm">
新建
</button>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,38 @@
<?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>
<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">
<header>
<button class="oe_stat_button" name="reminders_notice" string="批量催办" type="object" icon="fa-bars"/>
</header>
<field name="employee_id" widget="ztree_select"/>
<field name="notice_id"/>
<field name="notice_code"/>
<field name="state"/>
</list>
</field>
</record>
<record id="yuthon_confirm_users_filter" model="ir.ui.view">
<field name="name">yuthon.confirm.users.search</field>
<field name="model">yuthon.confirm.users</field>
<field name="arch" type="xml">
<search string="公告查询">
<group expand="0">
<filter name="group_state" string="确认状态" context="{'group_by': 'state'}"/>
</group>
</search>
</field>
</record>
<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>
</record>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_yuthon_cs_tree" model="ir.ui.view">
<field name="name">yuthon.cs.tree</field>
<field name="model">yuthon.cs.tree</field>
<field name="arch" type="xml">
<list string="Yuthon CS Tree">
<field name="name"/>
</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>
<field name="view_mode">list</field>
</record>
</odoo>

View File

@ -0,0 +1,33 @@
<?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>
<field name="model">yuthon.notice.type</field>
<field name="arch" type="xml">
<list string="公告类型" editable="bottom">
<field name="name"/>
<field name="note"/>
</list>
</field>
</record>
<record id="approval_notice_type_action" model="ir.actions.act_window">
<field name="name">公告类型管理</field>
<field name="res_model">yuthon.notice.type</field>
<field name="view_mode">list</field>
</record>
<menuitem
id="yuthon_notice_type_setting"
parent="yuthon_notice_menu_config_root"
name="配置"
sequence="7"/>
<menuitem id="yuthon_notice_type_menu"
parent="yuthon_notice_type_setting"
name="公告类型"
action="approval_notice_type_action"
sequence="1"/>
</odoo>

View File

@ -0,0 +1,216 @@
<?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>
<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'">
<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="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="sequence" column_invisible="1"/>
</list>
</field>
</record>
<record id="view_yuthon_notice_form" model="ir.ui.view">
<field name="name">yuthon.notice.form</field>
<field name="model">yuthon.notice</field>
<field name="arch" type="xml">
<form string="公告管理" create="0">
<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"/>
</group>
<group>
<field name="urgency_level" readonly="1"/>
</group>
<group>
<field name="is_this_company"/>
</group>
<group>
<field name="start_date"/>
</group>
</group>
<div class="col-12 text-center">
<h1>
<field name="name_title" readonly="1"/>
</h1>
</div>
<group col="4">
<group>
<field name="notice_type_id"/>
</group>
<group>
<field name="users_id"/>
</group>
<group>
<field name="company_id" invisible="1"/>
<field name="work_end" invisible="1"/>
<field name="department_id" string="发布部门" invisible="1"/>
<field name="create_date1"/>
</group>
</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>
<group col="3">
<group>
<field name="is_sticky" string="置顶"/>
</group>
<group>
<field name="is_change_notice"/>
</group>
<group>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="view_yuthon_notice_tree2" model="ir.ui.view">
<field name="name">yuthon.notice.tree</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">
<field name="name"/>
<field name="notice_type_id" string="类型"/>
<field name="users_id" string="发布人" class="list_ids_width"/>
<field name="is_notice_roger"/>
<field name="department_id" column_invisible="1"/>
<field name="users_ids" column_invisible="1"/>
<field name="create_date1"/>
<field name="end_date" column_invisible="1"/>
</list>
</field>
</record>
<record id="view_yuthon_notice_form2" model="ir.ui.view">
<field name="name">yuthon.notice.form2</field>
<field name="model">yuthon.notice</field>
<field name="arch" type="xml">
<form create="false" edit="false" delete="false" string="公告管理">
<group col="5">
<group>
<field name="notice_type_id" string="发布类型"/>
</group>
<group>
<field name="users_id"/>
</group>
<group>
<field name="read_number"/>
</group>
<group>
<field name="create_date1"/>
</group>
<group>
<field name="start_date"/>
</group>
</group>
<div class="col-12 text-center">
<h1>
<field name="name" required="1"/>
</h1>
</div>
<group>
<field name="notice_text" widget="tinymce" string=" "/>
<field name="documents_ids" widget="preview_many2many"/>
</group>
</form>
</field>
</record>
<record id="view_yuthon_notice_filter" model="ir.ui.view">
<field name="name">yuthon.notice.search</field>
<field name="model">yuthon.notice</field>
<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="department_id"/>
<filter string="未发布" name="no " domain="[('notice_state', '=', 'no')]"/>
<filter string="已发布" name="done " domain="[('notice_state', '=', 'done')]"/>
<group expand="0">
<filter name="group_statu" string="发布状态" context="{'group_by': 'notice_state'}"/>
<filter name="group_type" string="类型" context="{'group_by': 'notice_type_id'}"/>
<filter name="group_date" string="发布日期" context="{'group_by': 'create_date1'}"/>
</group>
</search>
</field>
</record>
<record id="approval_notice_action" model="ir.actions.act_window">
<field name="name">通知公告</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': '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="res_model">yuthon.notice</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="yuthon_notice_menu_config_root"
name="通知公告"
web_icon="yuthon_notice,static/description/icon.png"
sequence="1"/>
<!-- <menuitem id="yuthon_notice_menu1"-->
<!-- parent="yuthon_notice_menu_config_root"-->
<!-- name="通知公告"-->
<!-- action="approval_notice_action"-->
<!-- sequence="1"/>-->
<menuitem id="yuthon_notice_my_menu"
parent="yuthon_notice_menu_config_root"
name="通知公告管理"
action="approval_notice_action2"
sequence="2"/>
</odoo>

View File

@ -0,0 +1,2 @@
from . import models
from . import controllers

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
{
'name': "日程",
'summary': "日程事项",
'description': """""",
'author': 'zhou',
'version': '18.0.1.0.0',
'license': 'LGPL-3',
'data': [
# 'data/will_task_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',
'views/yuthon_dates_task_views.xml',
'views/yuthon_task_time_views.xml',
'views/yuthon_task_menu.xml',
],
'assets': {
'web.assets_backend': [
'yuthon_will_task/static/src/js/task_create_button.js',
'yuthon_will_task/static/src/xml/task_create_button.xml',
],
},
'demo': [],
'depends': ['hr', 'mail'],
'installable': True,
'application': True,
}

View File

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

View File

@ -0,0 +1,282 @@
import base64
import json
from datetime import timedelta
from odoo import http, fields, Command
from odoo.http import request
from odoo.osv import expression
import logging
_logger = logging.getLogger(__name__)
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,
})
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 '',
})
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,
}
# 转换 request_hour_from 和 request_hour_to 的显示值
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']
for f in request_hour_from_selection:
if f[0] == data.request_hour_from:
result['request_hour_from'] = f[1]
for f in request_hour_to_selection:
if f[0] == data.request_hour_to:
result['request_hour_to'] = f[1]
# 计算 start_date 星期几
if data.start_date:
result['start_date_week'] = data.start_date.strftime('%A')
return json.dumps({
'data': result,
'code': 200,
'message': 'success'
})
@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'])
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)
return json.dumps({
'data': result_list,
'code': 200,
'message': 'success'
})
@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)])
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,
})
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 '',
'weekday': weekday_str,
'request_hour_from': task.request_hour_from,
'request_hour_to': task.request_hour_to,
'remarks': task.remarks,
'address': task.address,
'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)
return json.dumps({
'data': result,
'code': 200,
'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

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<template id="mail_action_yuthon_will_task_add_comment">
<p style="margin: 0px;">
<span>日程事项提醒,</span><br />
<t t-set="name" t-value="'%s' % (object.name)"/>
<span style="margin-top: 8px;">请注意你的日程事项 <t t-esc="name" />
</span>
</p>
<p style="margin-top: 8px; margin-bottom: 10px;">
<a t-att-href="access_link" t-att-data-oe-model="object._name" t-att-data-oe-id="object.id" style="background-color:#2C65F7; padding: 5px; text-decoration: none; color: #fff; border-radius: 5px;">
详细信息 <t t-esc="model_description or '详情'"/>
</a>
</p>
</template>
</data>
</odoo>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="task_code_sequence" model="ir.sequence">
<field name="name">日程编号</field>
<field name="code">task_code</field>
<field name="prefix">RC%(y)s%(month)s%(day)s</field>
<field name="padding">3</field>
<field name="company_id" eval="False"/>
</record>
</odoo>

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="yuthon_task_time0" model="yuthon.task.time">
<field name="name">15分钟</field>
<field name="number">15</field>
<field name="task_unit">min</field>
</record>
<record id="yuthon_task_time1" model="yuthon.task.time">
<field name="name">30分钟</field>
<field name="number">30</field>
<field name="task_unit">min</field>
</record>
<record id="yuthon_task_time2" model="yuthon.task.time">
<field name="name">1小时</field>
<field name="number">1</field>
<field name="task_unit">hour</field>
</record>
<record id="yuthon_task_time3" model="yuthon.task.time">
<field name="name">2小时</field>
<field name="number">2</field>
<field name="task_unit">hour</field>
</record>
<record id="yuthon_task_time4" model="yuthon.task.time">
<field name="name">1天</field>
<field name="number">1</field>
<field name="task_unit">day</field>
</record>
</odoo>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<record forcecreate="True" id="will_task_time_data" model="ir.cron">
<field name="name">定时任务:日程任务提醒</field>
<field name="model_id" ref="model_yuthon_will_task"/>
<field name="state">code</field>
<field name="code">model.add_comment()</field>
<field eval="False" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="nextcall" eval="(DateTime.now() + timedelta(minutes=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
</record>
<record forcecreate="True" id="add_done_time_data" model="ir.cron">
<field name="name">定时任务:日程待办自动转为已办</field>
<field name="model_id" ref="model_yuthon_will_task"/>
<field name="state">code</field>
<field name="code">model.add_done()</field>
<field eval="False" name="active"/>
<field name="user_id" ref="base.user_root"/>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="nextcall" eval="(DateTime.now()).strftime('%Y-%m-%d 00:00:00')"/>
<field name="numbercall">-1</field>
<field eval="False" name="doall"/>
</record>
</data>
</odoo>

View File

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

View File

@ -0,0 +1,11 @@
from odoo import api, fields, models
class YuthonDatesTask(models.Model):
_name = "yuthon.dates.task"
_description = "日程任务"
reminder_time = fields.Datetime(string='提醒时间')
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

@ -0,0 +1,20 @@
from odoo import api, fields, models
class YuthonTaskTime(models.Model):
_name = 'yuthon.task.time'
_description = '提醒时间配置'
_rec_name = 'name'
name = fields.Char(string='名称')
number = fields.Integer(string="数值")
task_unit = fields.Selection([('min', '分钟'), ('hour', '小时'), ('day', '')], string="单位")
sequence = fields.Integer(string="序号")
@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:
n.name = str(n.number) + task_unit_dict[n.task_unit]

View File

@ -0,0 +1,397 @@
from odoo import api, fields, models
from datetime import datetime, timedelta, time, date
from odoo.exceptions import UserError, ValidationError
import logging
_logger = logging.getLogger(__name__)
class YuthonWillTask(models.Model):
_name = 'yuthon.will.task'
_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'),
('1', '01:00'), ('1.25', '01:15'), ('1.5', '01:30'), ('1.75', '01:45'),
('2', '02:00'), ('2.25', '02:15'), ('2.5', '02:30'), ('2.75', '02:45'),
('3', '03:00'), ('3.25', '03:15'), ('3.5', '03:30'), ('3.75', '03:45'),
('4', '04:00'), ('4.25', '04:15'), ('4.5', '04:30'), ('4.75', '04:45'),
('5', '05:00'), ('5.25', '05:15'), ('5.5', '05:30'), ('5.75', '05:45'),
('6', '06:00'), ('6.25', '06:15'), ('6.5', '06:30'), ('6.75', '06:45'),
('7', '07:00'), ('7.25', '07:15'), ('7.5', '07:30'), ('7.75', '07:45'),
('8', '08:00'), ('8.25', '08:15'), ('8.5', '08:30'), ('8.75', '08:45'),
('9', '09:00'), ('9.25', '09:15'), ('9.5', '09:30'), ('9.75', '09:45'),
('10', '10:00'), ('10.25', '10:15'), ('10.5', '10:30'), ('10.75', '10:45'),
('11', '11:00'), ('11.25', '11:15'), ('11.5', '11:30'), ('11.75', '11:45'),
('12', '12:00'), ('12.25', '12:15'), ('12.5', '12:30'), ('12.75', '12:45'),
('13', '13:00'), ('13.25', '13:15'), ('13.5', '13:30'), ('13.75', '13:45'),
('14', '14:00'), ('14.25', '14:15'), ('14.5', '14:30'), ('14.75', '14:45'),
('15', '15:00'), ('15.25', '15:15'), ('15.5', '15:30'), ('15.75', '15:45'),
('16', '16:00'), ('16.25', '16:15'), ('16.5', '16:30'), ('16.75', '16:45'),
('17', '17:00'), ('17.25', '17:15'), ('17.5', '17:30'), ('17.75', '17:45'),
('18', '18:00'), ('18.25', '18:15'), ('18.5', '18:30'), ('18.75', '18:45'),
('19', '19:00'), ('19.25', '19:15'), ('19.5', '19:30'), ('19.75', '19:45'),
('20', '20:00'), ('20.25', '20:15'), ('20.5', '20:30'), ('20.75', '20:45'),
('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)
hour = now.hour
return str(hour)
def _default_request_hour_to(self):
"""结束时间默认当前小时+123+1=0处理UTC+8时区"""
now = fields.Datetime.now() + timedelta(hours=8)
hour = (now.hour + 1) % 24
return str(hour)
request_hour_from = fields.Selection(time_list, string='开始时间', default=_default_request_hour_from)
request_hour_to = fields.Selection(time_list, string='结束时间', default=_default_request_hour_to)
priority = fields.Selection([('important', '重要不紧急'), ('urgent', '紧急不重要'), ('very_urgent', '重要且紧急')], string='优先级')
code = fields.Char(string="任务编号", default=lambda self: self.env['ir.sequence'].next_by_code('task_code'))
user_id = fields.Many2one('res.users', string="创建人", default=lambda self: self.env.user)
company_id = fields.Many2one(related="employee_id.company_id", string="公司", store=True)
name = fields.Text(string='任务内容')
remarks = fields.Text(string='备注')
employee_ids_short = fields.Char(string='参与人简略', compute='_compute_employee_ids_short')
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)
task_time_ids = fields.Many2many('yuthon.task.time', string='提醒')
document_ids = fields.Many2many('ir.attachment', string="附件")
address = fields.Char(string='地点')
start_date = fields.Date(string='开始日期', default=fields.Date.today)
end_date = fields.Date(string='结束日期', default=fields.Date.today)
task_time = fields.Char(string='任务时间', compute='_compute_task_time', store=True)
task_datetime = fields.Datetime(string='任务时间排序', compute='_compute_task_datetime', store=True)
is_this_company = fields.Boolean(string='本公司', default=True)
task_state = fields.Selection([('will', '待办'), ('done', '已办'), ('cancel', '取消')], default='will', string='状态', store=True)
users_ids = fields.Many2many('res.users', relation='yuthon_will_task_users_rel', column1='task_id', column2='user_id',
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])]
)
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')
task_type = fields.Selection([
('task', '日程'),
('meet', '会议')
], string='类型', default='task')
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'
@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:
raise ValidationError("开始日期必须小于结束日期,请重新设置。")
def open_record(self):
"""打开当前日程记录的表单视图"""
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'name': '日程详情',
'res_model': self._name,
'res_id': self.id,
'view_mode': 'form',
'view_id': self.env.ref('yuthon_will_task.view_yuthon_will_task_form').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:
record.task_week = weekdays[record.start_date.weekday()]
@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:
record._compute_task_time()
@api.depends('start_date', 'request_hour_from')
def _compute_task_datetime(self):
"""将 start_date + request_hour_from 拼合为 Datetime用于排序"""
for record in self:
if record.start_date and record.request_hour_from:
try:
hour_float = float(record.request_hour_from)
hours = int(hour_float)
minutes = int(round((hour_float - hours) * 60))
# 存储时转为UTC界面显示是UTC+8存储需减8小时
record.task_datetime = datetime(
record.start_date.year,
record.start_date.month,
record.start_date.day,
hours, minutes, 0
) - timedelta(hours=8)
except Exception:
record.task_datetime = False
else:
record.task_datetime = False
@api.depends('start_date', 'task_week', 'request_hour_from', 'task_type')
def _compute_task_time(self):
"""计算任务时间显示文本"""
for record in self:
date_str = ""
if record.start_date:
date_str = record.start_date.strftime("%Y/%m/%d")
hour_mapping = {
'0': '0:00', '0.25': '0:15', '0.5': '0:30', '0.75': '0:45',
'1': '01:00', '1.25': '01:15', '1.5': '01:30', '1.75': '01:45',
'2': '02:00', '2.25': '02:15', '2.5': '02:30', '2.75': '02:45',
'3': '03:00', '3.25': '03:15', '3.5': '03:30', '3.75': '03:45',
'4': '04:00', '4.25': '04:15', '4.5': '04:30', '4.75': '04:45',
'5': '05:00', '5.25': '05:15', '5.5': '05:30', '5.75': '05:45',
'6': '06:00', '6.25': '06:15', '6.5': '06:30', '6.75': '06:45',
'7': '07:00', '7.25': '07:15', '7.5': '07:30', '7.75': '07:45',
'8': '08:00', '8.25': '08:15', '8.5': '08:30', '8.75': '08:45',
'9': '09:00', '9.25': '09:15', '9.5': '09:30', '9.75': '09:45',
'10': '10:00', '10.25': '10:15', '10.5': '10:30', '10.75': '10:45',
'11': '11:00', '11.25': '11:15', '11.5': '11:30', '11.75': '11:45',
'12': '12:00', '12.25': '12:15', '12.5': '12:30', '12.75': '12:45',
'13': '13:00', '13.25': '13:15', '13.5': '13:30', '13.75': '13:45',
'14': '14:00', '14.25': '14:15', '14.5': '14:30', '14.75': '14:45',
'15': '15:00', '15.25': '15:15', '15.5': '15:30', '15.75': '15:45',
'16': '16:00', '16.25': '16:15', '16.5': '16:30', '16.75': '16:45',
'17': '17:00', '17.25': '17:15', '17.5': '17:30', '17.75': '17:45',
'18': '18:00', '18.25': '18:15', '18.5': '18:30', '18.75': '18:45',
'19': '19:00', '19.25': '19:15', '19.5': '19:30', '19.75': '19:45',
'20': '20:00', '20.25': '20:15', '20.5': '20:30', '20.75': '20:45',
'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'
}
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
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)
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':
return timedelta(hours=task_time.number)
elif task_time.task_unit == 'day':
return timedelta(days=task_time.number)
else:
return timedelta.max
def add_done(self):
"""定时任务:将过期的待办日程自动标记为已办"""
today = date.today()
records = self.search([
('task_state', '=', 'will'),
('end_date', '<', today),
('end_date', '!=', False),
])
if records:
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))
])
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}')
_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:
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',
)
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',
)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': '已发送提醒消息',
'sticky': False,
}
}
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',
)
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'type': 'success',
'message': '已发送取消提醒消息',
'sticky': False,
}
}
@api.model
def create(self, vals):
"""创建日程时,同步创建提醒任务"""
will_task = super(YuthonWillTask, self).create(vals)
will_task._sync_dates_task()
return will_task
def write(self, vals):
"""修改日程时,先删除旧提醒任务,再创建新的"""
res = super(YuthonWillTask, self).write(vals)
key_fields = ['start_date', 'request_hour_from', 'task_time_ids', 'employee_ids', 'task_type']
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
if not reminder_employees:
return
old_tasks = self.env['yuthon.dates.task'].search([('yuthon_will_task_id', '=', self.id), ('task_state', '=', 'to_do')])
if old_tasks:
old_tasks.unlink()
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))
for task_time in self.task_time_ids:
reminder_time_str = task_time.name
reminder_hours = 0.0
import re
match = re.match(r'(\d+)(分钟|小时|天)', reminder_time_str.strip())
num = int(match.group(1))
unit = match.group(2)
if unit == '小时':
reminder_hours = num
elif unit == '分钟':
reminder_hours = num / 60.0
elif unit == '':
reminder_hours = num * 24.0
reminder_datetime = task_start_datetime - timedelta(hours=reminder_hours)
now = fields.Datetime.now() + timedelta(hours=8)
if now > reminder_datetime:
continue
existing_dates_task = self.env['yuthon.dates.task'].search([
('yuthon_will_task_id', '=', self.id),
('reminder_time', '=', reminder_datetime)
])
task_vals = {
'reminder_time': reminder_datetime - timedelta(hours=8),
'employee_ids': [(6, 0, reminder_employees.ids)],
'task_state': 'to_do',
}
if existing_dates_task:
existing_dates_task.write(task_vals)
else:
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': '提醒任务详情',
'view_mode': 'tree',
'res_model': 'yuthon.dates.task',
'domain': [('yuthon_will_task_id', '=', self.id)],
}

View File

@ -0,0 +1,7 @@
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
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
3 access_yuthon_task_time_manager yuthon_task_time_manager model_yuthon_task_time base.group_system 1 1 1 1
4 access_yuthon_will_task yuthon_will_task model_yuthon_will_task base.group_user 1 1 1 0
5 access_yuthon_will_task_manager yuthon_will_task_manager model_yuthon_will_task base.group_system 1 1 1 1
6 access_yuthon_dates_task yuthon_dates_task model_yuthon_dates_task base.group_user 1 1 1 0
7 access_yuthon_dates_task_manager yuthon_dates_task_manager model_yuthon_dates_task base.group_system 1 1 1 1

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="module_yuthon_will_task" model="ir.module.category">
<field name="name">日程组</field>
</record>
<record id="group_yuthon_will_task_user" model="res.groups">
<field name="name">日程-用户</field>
<field name="category_id" ref="module_yuthon_will_task"/>
</record>
<record id="group_yuthon_will_task_leader" model="res.groups">
<field name="name">日程-管理员</field>
<field name="category_id" ref="module_yuthon_will_task"/>
<field name="implied_ids" eval="[(4, ref('group_yuthon_will_task_user'))]"/>
</record>
</data>
</odoo>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -0,0 +1,28 @@
/** @odoo-module */
import { ListController } from "@web/views/list/list_controller";
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export class CustomTaskListController extends ListController {
setup() {
super.setup();
}
openTaskForm() {
this.actionService.doAction({
type: 'ir.actions.act_window',
res_model: 'yuthon.will.task',
name: '日程会议',
view_mode: 'form',
view_type: 'form',
views: [[false, 'form']],
target: 'current',
context: {'task_state': 'will'},
});
}
}
registry.category("views").add("custom_task_tree_button", {
...listView,
Controller: CustomTaskListController,
buttonTemplate: "yuthon_task.ListView.Buttons",
});

View File

@ -0,0 +1,4 @@
.button_width{
width: 100px !important;
max-width: 100px !important;
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<templates>
<t t-name="yuthon_task.ListView.Buttons" t-inherit="web.ListView.Buttons">
<xpath expr="//div[hasclass('o_list_buttons')]" position="after">
<button type="button" class="btn btn-primary" style="margin-left: 10px;" t-on-click="openTaskForm">
新建日程
</button>
</xpath>
</t>
</templates>

View File

@ -0,0 +1,38 @@
<?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>
<field name="model">yuthon.dates.task</field>
<field name="arch" type="xml">
<tree string="日程任务" editable="bottom"
decoration-success="task_state == 'to_do'"
decoration-danger="task_state == 'done'"
>
<field name="yuthon_will_task_id"/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="reminder_time"/>
<field name="task_state"/>
</tree>
</field>
</record>
<record id="view_yuthon_dates_task_search" model="ir.ui.view">
<field name="name">yuthon.dates.task.search</field>
<field name="model">yuthon.dates.task</field>
<field name="arch" type="xml">
<search string="日程任务">
<field name="yuthon_will_task_id"/>
<group expand="0" string="Group By">
<filter name="task_state" string="状态" context="{'group_by':'task_state'}"/>
</group>
</search>
</field>
</record>
<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="search_view_id" ref="view_yuthon_dates_task_search"/>
<field name="context">{'search_default_task_state':1}</field>
</record>
</odoo>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<menuitem id='menu_yuthon_will_task'
name='会议日程'
web_icon="yuthon_will_task,static/description/icon.png"
sequence='10'/>
<menuitem id='menu_yuthon_will_task_root2'
name='日程管理'
parent='menu_yuthon_will_task'
action='view_yuthon_will_task_action2'
sequence='10'/>
<menuitem id="task_type_menu"
name="配置"
parent="menu_yuthon_will_task"
sequence="100"/>
<menuitem id="yuthon_task_time_setting_menu"
name="提醒时间"
parent="task_type_menu"
action="yuthon_task_time_setting_action"
sequence="5"/>
<menuitem id="yuthon_dates_task_menu"
name="日程任务"
parent="task_type_menu"
action="view_yuthon_dates_task_action"
sequence="5"/>
</odoo>

View File

@ -0,0 +1,21 @@
<?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>
<field name="model">yuthon.task.time</field>
<field name="arch" type="xml">
<tree 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>
</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>
</record>
</odoo>

View File

@ -0,0 +1,371 @@
<?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>
<field name='model'>yuthon.will.task</field>
<field name='arch' type='xml'>
<tree string='日程'
decoration-muted="task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
decoration-success="priority == 'important'" limit="15" action="open_record" type="object"
>
<field name="task_type"/>
<field name='start_date' column_invisible="1"/>
<field name='task_time'/>
<field name='name' required="1"/>
<field name='employee_ids' widget="many2many_tags" required="1" options="{'no_create': True}"/>
<field name="address"/>
<field name='remarks'/>
<field name='priority'/>
<field name='task_state' readonly="1"/>
<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'"/>
<button name="schedule_reminders" string="提醒任务" type="object" class="btn btn-blue"/>
</tree>
</field>
</record>
<record id='yuthon_will_task_search' model='ir.ui.view'>
<field name='name'>yuthon.will.task.search</field>
<field name='model'>yuthon.will.task</field>
<field name='arch' type='xml'>
<search string='日程'>
<field name="name"/>
<filter name="next_seven_days" string="周内事项" domain="[('start_date', '>=', context_today().strftime('%Y-%m-%d')),
('start_date', '&lt;=', (context_today() + relativedelta(days=7)).strftime('%Y-%m-%d'))]"/>
<filter name="next_thirty_days" string="月内事项" domain="[('start_date', '>=', context_today().strftime('%Y-%m-%d')),
('start_date', '&lt;=', (context_today() + relativedelta(days=30)).strftime('%Y-%m-%d'))]"/>
</search>
</field>
</record>
<record id="view_yuthon_will_task_form" model="ir.ui.view">
<field name="name">yuthon.will.task.form</field>
<field name="model">yuthon.will.task</field>
<field name="arch" type="xml">
<form string="日程">
<sheet>
<widget name="web_ribbon" title="重要不紧急" bg_color="text-bg-success"
invisible="priority != 'important'"/>
<widget name="web_ribbon" title="紧急不重要" bg_color="text-bg-warning"
invisible="priority != 'urgent'"/>
<widget name="web_ribbon" title="重要且紧急" bg_color="text-bg-danger"
invisible="priority != 'very_urgent'"/>
<group col="3">
<group>
<field name="code" readonly="1"/>
</group>
<group>
<field name="is_this_company" string="本公司"/>
</group>
<group>
<field name="priority"/>
<field name="task_type" invisible="1"/>
</group>
</group>
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" required="1"/>
</h1>
</div>
<group col="2">
<group>
<label for="start_date" string="开始日期"/>
<div class="d-inline-flex w-100">
<field name="start_date" string="开始日期" class="me-2"
style="max-width: 6rem !important;"/>
<field name="task_week" string="" class="me-2" style="max-width: 4rem !important;"/>
<field name="request_hour_from" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
</group>
</group>
<group col="2">
<group invisible="task_type != 'meet'">
<label for="end_date" string="结束日期"/>
<div class="d-inline-flex w-100">
<field name="end_date" string="结束日期" class="me-2"
style="max-width: 6rem !important;"/>
<field name="task_week" string="" class="me-2" style="max-width: 4rem !important;"/>
<field name="request_hour_to" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
</group>
<group invisible="task_type == 'meet'">
<label for="end_date" string="结束日期"/>
<div class="d-inline-flex w-100">
<field name="end_date" string="结束日期" class="me-2"
style="max-width: 6rem !important;"/>
<field name="task_week2" string="" class="me-2" style="max-width: 4rem !important;"/>
<field name="request_hour_to" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
</group>
</group>
<group>
<field name="task_time_ids" widget="many2many_tags" required="1"/>
</group>
<group>
<field name="employee_id"/>
<field name="task_state" invisible="1"/>
<field name="company_id" invisible="1"/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"
domain="[('company_id', '=', company_id)] if is_this_company else []"/>
</group>
<group col="2">
<group>
<field name="address"/>
</group>
</group>
<group>
<field name="remarks"/>
<field name="document_ids" widget="many2many_binary"/>
<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>
<field name='model'>yuthon.will.task</field>
<field name='arch' type='xml'>
<tree string='日程'
decoration-muted="task_state == 'cancel'"
decoration-danger="priority == 'very_urgent'"
decoration-info="priority == 'urgent'"
decoration-success="priority == 'important'" create="false" edit="false"
js_class="custom_task_tree_button" limit="15" default_order="task_datetime desc">
<field name='task_datetime' column_invisible="1"/>
<field name='start_date' column_invisible="1"/>
<field name='task_time'/>
<field name='name' required="1"/>
<field name='employee_ids' widget="many2many_tags" required="1" options="{'no_create': True}"/>
<field name="address"/>
<field name='remarks'/>
<field name='priority'/>
<field name='task_state' readonly="1"/>
<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>
</field>
</record>
<record id="view_yuthon_will_task_form2" model="ir.ui.view">
<field name="name">yuthon.will.task.message.form</field>
<field name="model">yuthon.will.task</field>
<field name="arch" type="xml">
<form string="日程" create="false">
<sheet>
<widget name="web_ribbon" title="重要不紧急" bg_color="text-bg-success"
invisible="priority != 'important'"/>
<widget name="web_ribbon" title="紧急不重要" bg_color="text-bg-warning"
invisible="priority != 'urgent'"/>
<widget name="web_ribbon" title="重要且紧急" bg_color="text-bg-danger"
invisible="priority != 'very_urgent'"/>
<group col="3">
<group>
<field name="code" readonly="1"/>
</group>
<group>
<field name="is_this_company" string="本公司"/>
</group>
<group>
<field name="priority"/>
<field name="task_type" invisible="1"/>
</group>
</group>
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" required="1"/>
</h1>
</div>
<group col="2">
<group>
<label for="start_date" string="开始日期"/>
<div class="d-inline-flex w-100">
<field name="start_date" string="开始日期" class="me-2"
style="max-width: 6rem !important;"/>
<field name="task_week" string="" class="me-2" style="max-width: 4rem !important;"/>
<field name="request_hour_from" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
</group>
</group>
<group col="2">
<group invisible="task_type != 'meet'">
<label for="end_date" string="结束日期"/>
<div class="d-inline-flex w-100">
<field name="end_date" string="结束日期" class="me-2"
style="max-width: 6rem !important;"/>
<field name="task_week" string="" class="me-2" style="max-width: 4rem !important;"/>
<field name="request_hour_to" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
</group>
<group invisible="task_type == 'meet'">
<label for="end_date" string="结束日期"/>
<div class="d-inline-flex w-100">
<field name="end_date" string="结束日期" class="me-2"
style="max-width: 6rem !important;"/>
<field name="task_week2" string="" class="me-2" style="max-width: 4rem !important;"/>
<field name="request_hour_to" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
</group>
</group>
<group>
<field name="task_time_ids" widget="many2many_tags" required="1"/>
</group>
<group>
<field name="employee_id" domain="[('company_id', '=', company_id)]"/>
<field name="company_id" invisible="1"/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name='users_ids' widget="many2many_tags" invisible="1"/>
</group>
<group col="2">
<group>
<field name="address"/>
</group>
</group>
<group>
<field name="remarks"/>
<field name="document_ids" widget="many2many_binary"/>
<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_kanban" model="ir.ui.view">
<field name="name">yuthon.will.task.kanban</field>
<field name="model">yuthon.will.task</field>
<field name="arch" type="xml">
<kanban>
<field name="name"/>
<field name="task_state"/>
<field name="start_date"/>
<field name="end_date"/>
<field name="task_week"/>
<field name="request_hour_from"/>
<field name="request_hour_to"/>
<field name="employee_ids"/>
<field name="address"/>
<field name="remarks"/>
<templates>
<t t-name="kanban-box">
<div class="oe_kanban_global_click" style="border-radius: 8px; padding: 12px; margin-bottom: 8px;">
<!-- 标题行:任务名称 + 状态标签 -->
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px;">
<div style="font-weight: bold; font-size: 14px; line-height: 1.5; flex: 1; margin-right: 8px;">
<field name="name"/>
</div>
<div>
<field name="task_state" widget="badge"
decoration-success="task_state == 'done'"
decoration-info="task_state == 'will'"
decoration-muted="task_state == 'cancel'"/>
</div>
</div>
<!-- 时间行 -->
<div style="color: #555; font-size: 13px; margin-bottom: 4px;">
<field name="start_date"/>
<t t-if="record.task_week.raw_value">
<t t-esc="record.task_week.raw_value"/>
</t>
<t t-if="record.request_hour_from.value and record.request_hour_to.value">
<t t-esc="record.request_hour_from.value"/> - <t t-esc="record.request_hour_to.value"/>
</t>
</div>
<!-- 参与人 -->
<t t-if="record.employee_ids.raw_value and record.employee_ids.raw_value.length">
<div style="color: #555; font-size: 13px; margin-bottom: 4px;">
参与人 <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"/>
</div>
</t>
<!-- 备注 -->
<t t-if="record.remarks.raw_value">
<div style="color: #555; font-size: 13px;">
备注:<field name="remarks"/>
</div>
</t>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<record id="view_calendar_event_calendar" model="ir.ui.view">
<field name="name">yuthon.will.task.calendar</field>
<field name="model">yuthon.will.task</field>
<field name="priority" eval="2"/>
<field name="arch" type="xml">
<calendar string="日程"
date_start="start_date"
event_open_popup="true"
event_limit="5"
quick_create="false"
quick_create_view_id="%(yuthon_will_task.view_yuthon_will_task_form)d"
color="employee_ids">
<field name="priority"/>
</calendar>
</field>
</record>
<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='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': '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="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': 'kanban', 'view_id': ref('yuthon_will_task.view_yuthon_will_task_kanban')})]" />
</record>
<record id="all_yuthon_will_task" model="ir.actions.server">
<field name="name">更新</field>
<field name="model_id" ref="yuthon_will_task.model_yuthon_will_task"/>
<field name="binding_model_id" ref="yuthon_will_task.model_yuthon_will_task"/>
<field name="state">code</field>
<field name="code">action = model.one_time()</field>
</record>
<record id="all_update_task_state" model="ir.actions.server">
<field name="name">状态改为已办</field>
<field name="model_id" ref="yuthon_will_task.model_yuthon_will_task"/>
<field name="binding_model_id" ref="yuthon_will_task.model_yuthon_will_task"/>
<field name="state">code</field>
<field name="code">action = model.update_task_state()</field>
<field name="groups_id" eval="[(4, ref('base.group_system'))]"/>
</record>
</odoo>