添加功能

This commit is contained in:
李鹏宇
2026-01-05 20:32:26 +08:00
parent fad439565f
commit 0c11940e58
14 changed files with 200 additions and 66 deletions
+2 -1
View File
@@ -7,11 +7,12 @@
'description': 'Experience the Ease of Booking Meeting Rooms with Real-Time Availability Display.',
'category': 'Services/Room',
'version': '1.0',
'depends': ['mail', 'hr', 'documents', 'yuthon_wechat', 'fleet', 'calendar', 'yuthon_hr_employee'],
'depends': ['mail', 'hr', 'documents', 'yuthon_wechat', 'fleet', 'calendar', 'yuthon_hr_employee', 'soong_workflow',],
'data': [
'data/message_template_views.xml',
'data/room_wechat_cron_data.xml',
'data/ir_module_category_data.xml',
'data/room_code_data.xml',
'data/room_meeting_type_data.xml',
'security/ir_rule.xml',
'security/ir.model.access.csv',
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="room_booking_approval_sequence" model="ir.sequence">
<field name="name">议题会议编号</field>
<field name="code">room_booking_approval_code</field>
<field name="prefix">YTHY%(y)s%(month)s%(day)s</field>
<field name="padding">3</field>
<field name="company_id" eval="False"/>
</record>
</odoo>
+21 -3
View File
@@ -12,11 +12,12 @@ from datetime import timedelta
"""富文本字段保存无效"""
class RoomBooking(models.Model):
_name = "room.booking"
_inherit = ["mail.thread"]
_inherit = ['mail.thread.main.attachment', 'mail.activity.mixin', 'soong.workflow.mixin']
_description = "Room Booking"
_order = "start_datetime desc, id"
name = fields.Char(string="标题", required=True, tracking=1)
code = fields.Char(string="流水号", default=lambda self: self.env['ir.sequence'].next_by_code('room_booking_approval_code'))
room_id = fields.Many2one("room.room", string="Room", required=True, ondelete="cascade",
group_expand="_read_group_room_id", tracking=4)
start_datetime = fields.Datetime(string="Start Datetime", required=True, tracking=2)
@@ -33,8 +34,24 @@ class RoomBooking(models.Model):
task_time_ids = fields.Many2many('yuthon.task.time', string='提醒')
meeting_type = fields.Selection([('up', '线上'), ('down', '线下'), ('exterior', '外部')], default='down', string="会议形式",
required=True)
urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度',
default='normal')
meeting_type_id = fields.Many2one('room.meeting.type', string="会议类型")
room_users_ids = fields.Many2many('res.users', string="出席人员")
self_users_ids = fields.Many2many(
'res.users',
string="当前经办人",
relation='issue_room_approval_self_users_res_users_rel',
column1='approval_id',
column2='self_user_id',
default=lambda self: [(6, 0, [self.env.user.id])]
)
room_users_ids = fields.Many2many(
'res.users',
string="出席人员",
relation='issue_room_approval_room_users_res_users_rel',
column1='approval_id',
column2='room_user_id'
)
room_users_names = fields.Char(string="出席人员char", compute="_compute_room_users", store=True)
other_company_id = fields.Many2one('res.partner', string="外部公司")
host = fields.Many2one('hr.employee', string="主持人")
@@ -52,7 +69,8 @@ class RoomBooking(models.Model):
driver = fields.Many2one('hr.employee', string="司机")
partner_ids = fields.Many2many('res.partner', string="日程安排人员")
is_this_company = fields.Boolean(string='本公司', default=True)
work_end = fields.Boolean(string="结束")
name_title = fields.Char(string="议题会议审批")
# 关联流程
relevancy_type = fields.Selection([('none', '无')], string="关联流程类型")
+26 -4
View File
@@ -130,13 +130,32 @@
<form duplicate="0">
<sheet>
<header>
<button name="meet_notice" string="会议通知" type="object" class="oe_highlight"/>
<button name="meeting_sign_in" string="签到" invisible="meeting_type != 'down'" type="object"
<button name="meet_notice" string="会议通知" type="object" class="oe_highlight" invisible="not work_end"/>
<button name="meeting_sign_in" string="签到" invisible="meeting_type != 'down' or not work_end " type="object"
class="oe_highlight"/>
<button name="meeting_partner_create" string="录入" invisible="meeting_type != 'down'"
<button name="meeting_partner_create" string="录入" invisible="meeting_type != 'down' or not work_end"
type="object" class="oe_highlight"/>
<button name="sync_wechat_room" string="同步企业微信" type="object" class="oe_highlight"/>
<button name="sync_wechat_room" string="同步企业微信" type="object" class="oe_highlight" invisible="not work_end"/>
</header>
<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="3">
<group>
<field name="code" string="流水号" readonly="1"/>
</group>
<group>
<field name="urgency_level" readonly="1"/>
</group>
</group>
<div class="col-12 text-center">
<h1>
<field name="name_title" readonly="1" string="议题会议审批"/>
</h1>
</div>
<div class="oe_title">
<label for="name"/>
<h1>
@@ -153,6 +172,9 @@
</group>
<group>
<group>
<field name="state" invisible="1"/>
<field name="is_back" invisible="1"/>
<field name="work_end" invisible="1"/>
<field name="organizer_id" options="{'no_create': True}"/>
<field name="meeting_type"/>
<field name="driver" invisible="meeting_type != 'exterior'"/>
@@ -24,15 +24,13 @@ class SoongBouncedWizard(models.Model):
if invalid_logs:
raise UserError('当前状态不可以收回')
related_record = self.env[self.res_model].browse(self.res_id)
prev_log = None
if hasattr(related_record, 'self_users_ids'):
total_log_count = len(log_ids)
deleted_log_count = len(log_to_delete)
prev_log_index = (total_log_count - deleted_log_count) - 1
if prev_log_index < 0 or prev_log_index >= len(log_ids):
raise UserError('无有效的上一级审批明细行,无法执行收回操作')
prev_log = log_ids[prev_log_index]
total_log_count = len(log_ids)
deleted_log_count = len(log_to_delete)
prev_log_index = (total_log_count - deleted_log_count) - 1
if prev_log_index < 0 or prev_log_index >= len(log_ids):
raise UserError('无有效的上一级审批明细行,无法执行收回操作')
prev_log = log_ids[prev_log_index]
related_record = self.env[self.res_model].browse(self.res_id)
if hasattr(related_record, 'self_users_ids'):
@@ -454,7 +454,7 @@ class SoongWorkflowCommentWizard(models.TransientModel):
fields.Datetime.now() + timedelta(
hours=8)),
url=url, btntxt="查看详情")
next_node_employee_log.msg_id = result_data[1]['msgid']
# next_node_employee_log.msg_id = result_data[1]['msgid']
else: # 串审模式 (沿用原有逻辑)
# 查找下一个节点
@@ -724,7 +724,7 @@ class SoongWorkflowCommentWizard(models.TransientModel):
# recall_url = f'https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token={access_token}'
record = self.env[self.res_model].search([('id', '=', self.res_id)])
log_ids = record.log_ids.sorted('sequence')
if self.env.user.name != ['Administrator', '叶华锋']:
if self.env.user.name not in ['Administrator', '叶华锋']:
if log_ids[-1].approval_state in ['1', '2']:
raise UserError('当前状态不可以收回')
related_record = self.env[self.res_model].browse(self.res_id)
@@ -741,7 +741,7 @@ class SoongWorkflowCommentWizard(models.TransientModel):
record.write({'state': 'draft', 'work_end': False})
else:
record.write({'state': log_ids[-2].name, 'work_end': False})
return self.node_btn_id.with_context(cx).run()
return self.node_btn_id.with_context(cx).run()
def update_self_users_ids(self):
@@ -1,7 +1,6 @@
from datetime import datetime, timedelta, time
from email.policy import default
from operator import index
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import calendar
@@ -724,6 +723,7 @@ class YuthonVacationReports(models.Model):
record.statutory_leave = legal_total/8
record.cop_leave = total_add2 / 8
@api.depends('meal_fee_ids.update_lines', 'meal_fee_ids.update_type', 'meal_fee_ids.is_void')
def _compute_meal_fee(self):
for record in self:
@@ -962,6 +962,35 @@ class VacationOverline(models.Model):
vacation_main = self.env['yuthon.vacation.reports'].browse(vals['vacation_id'])
if vacation_main.vacation_state == 'confirmed':
vals['is_confirm'] = True
if vals.get('update_type') == 'reduce' and vals.get('vacation_id'):
vacation_main = self.env['yuthon.vacation.reports'].browse(vals['vacation_id'])
deduct_workday = vals.get('workday', 0.0)
deduct_public = vals.get('public_holidays', 0.0)
if vacation_main:
vacation_deduct_vals = {
'year_workday': vacation_main.year_workday - deduct_workday/8,
'year_holiday': vacation_main.year_holiday - deduct_public/8,
}
vacation_main.write(vacation_deduct_vals)
if vacation_main.vacation_date:
current_vacation_date = vacation_main.vacation_date
next_month_first_day = current_vacation_date + relativedelta(months=+1, day=1)
# 必须是跨年才写入上年工作日
is_cross_year = current_vacation_date.year != next_month_first_day.year
next_month_vacation = self.env['yuthon.vacation.reports'].search([
('employee_id', '=', vacation_main.employee_id.id),
('vacation_date', '=', next_month_first_day),
('vacation_state', 'in', ['not_sent', 'pending']),
], limit=1)
if is_cross_year:
next_month_deduct_vals = {
'last_workday': next_month_vacation.last_workday - deduct_workday/8,
'last_holiday': next_month_vacation.last_holiday - deduct_public/8,
}
next_month_vacation.write(next_month_deduct_vals)
return super(VacationOverline, self).create(vals)
# def unlink(self):
@@ -72,6 +72,9 @@
<field name="department_id" column_invisible="1"/>
<field name="company_id" column_invisible="1"/>
<field name="vacation_date" column_invisible="1"/>
<field name="year_distribution"/>
<field name="used_this_year"/>
<field name="remainder_year"/>
<field name="should_attendance" options='{ "fg_color": "white: should_attendance == False"}'/>
<field name="actual_attendance" options='{ "fg_color": "white: actual_attendance == False"}'/>
<field name="right_days" options='{ "fg_color": "white: right_days == False"}'/>
+56 -19
View File
@@ -11,16 +11,17 @@ class RoomBooking(models.Model):
number_of_meetings = fields.Integer(string='会议次数')
minutes_id = fields.Many2one('issue.meeting.minutes', string='会议纪要流程')
@api.constrains('start_datetime')
def _check_start_datetime(self):
def end_approval(self):
self.write({'work_end': True})
for meet in self.issue_meeting_line_ids:
if meet.issue_library_id:
if meet.issue_library_id.is_party == True:
meet.issue_library_id.party_date = self.start_datetime + timedelta(hours=8)
if meet.issue_library_id.is_class == True:
meet.issue_library_id.class_date = self.start_datetime + timedelta(hours=8)
if meet.issue_library_id.is_boss == True:
meet.issue_library_id.boss_date = self.start_datetime + timedelta(hours=8)
meeting_type_name = str(self.meeting_type_id.name).strip()
if '党' in meeting_type_name:
meet.issue_library_id.party_char = str(self.start_datetime.date()) + self.meeting_type_id.name
if '班子' in meeting_type_name:
meet.issue_library_id.class_char = str(self.start_datetime.date()) + f'第{self.number_of_meetings}' + self.meeting_type_id.name
if '董事' in meeting_type_name:
meet.issue_library_id.boss_char = str(self.start_datetime.date()) + self.meeting_type_id.name
def bottom_meeting_approval(self):
@@ -51,17 +52,27 @@ class RoomBooking(models.Model):
@api.model_create_multi
def create(self, vals_list):
# 步骤1:先调用父类create方法,批量创建主记录,获取有效记录集
res = super().create(vals_list)
for vals in vals_list:
if vals['issue_meeting_line_ids']:
for line in vals['issue_meeting_line_ids']:
self.env['issue.library.room.line'].create({
'issue_library_id': line[2]['issue_library_id'],
'room_booking_id': res.id,
'room_id': vals['room_id'],
'meeting_type_id': vals['meeting_type_id'],
'approval_date': vals['start_datetime'],
})
if not res:
return res
for idx, (main_record, vals) in enumerate(zip(res, vals_list)):
issue_meeting_lines = vals.get('issue_meeting_line_ids', [])
for line_triple in issue_meeting_lines:
line_state, line_id, line_vals = line_triple
issue_library_id = line_vals.get('issue_library_id')
room_id = vals.get('room_id')
meeting_type_id = vals.get('meeting_type_id')
approval_date = vals.get('start_datetime')
if not (issue_library_id and main_record.id and room_id and meeting_type_id):
continue
self.env['issue.library.room.line'].create({
'issue_library_id': issue_library_id,
'room_booking_id': main_record.id,
'room_id': room_id,
'meeting_type_id': meeting_type_id,
'approval_date': approval_date,
})
return res
@@ -77,5 +88,31 @@ class IssueMeetingLine(models.Model):
employee_id = fields.Many2one('hr.employee', string='传达人')
attendees = fields.Char(string='列席人员')
is_sanda = fields.Boolean(string="三重一大")
decision_results = fields.Selection([('agree', '通过'), ('disagree', '未通过')], string="决策结果")
decision_results = fields.Selection([('agree', '通过'), ('disagree', '重新上会'), ('rejected', '驳回')], string="决策结果")
decision_details = fields.Text(string="决策详情")
@api.onchange('decision_results', 'meeting_booking_id')
def _onchange_decision_results(self):
if not self.meeting_booking_id.meeting_type_id:
return
meeting_type_name = str(self.meeting_booking_id.meeting_type_id.name).strip()
if self.decision_results == 'agree':
is_agree = 'pass'
agree_name = '通过'
else:
is_agree = 'reject'
agree_name = '未通过'
if '党' in meeting_type_name:
self.issue_library_id.is_party_passed = is_agree
original_party_char = self.issue_library_id.party_char or ""
self.issue_library_id.party_char = original_party_char + (agree_name)
if '班子' in meeting_type_name:
self.issue_library_id.is_class_passed = is_agree
original_class_char = self.issue_library_id.class_char or ""
self.issue_library_id.class_char = original_class_char + (agree_name)
if '董事' in meeting_type_name:
self.issue_library_id.is_boss_passed = is_agree
original_boss_char = self.issue_library_id.boss_char or ""
self.issue_library_id.boss_char = original_boss_char + (agree_name)
+9
View File
@@ -59,6 +59,12 @@ class IssueApproval(models.Model):
else:
rec.issue_display = ""
def end_approval(self):
for rec in self:
if rec.issue_approval_ids:
for issue_library in rec.issue_approval_ids:
issue_library.issue_library_id.write({'issue_state': 'will'})
class IssueApprovalLine(models.Model):
_name = 'issue.approval.line'
_description = '关联议题明细'
@@ -81,3 +87,6 @@ class IssueApprovalLine(models.Model):
'view_mode': 'form',
'target': 'current',
}
class IssueRoomApproval(models.Model):
_name = 'issue.room.approval'
+14 -12
View File
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, tools
from odoo.exceptions import UserError, ValidationError
from datetime import datetime, timedelta, date
class IssueLibrary(models.Model):
@@ -23,11 +24,11 @@ class IssueLibrary(models.Model):
('will', '待上会'), ('on_meet', '已上会'), ('cancel', '已取消')],
default='draft', string="议题状态")
is_party = fields.Boolean(string="党总支", store=True)
party_date = fields.Datetime(string=' ')
party_char = fields.Char(string=' ')
is_class = fields.Boolean(string="班子会", store=True)
class_date = fields.Datetime(string=' ')
class_char = fields.Char(string=' ')
is_boss = fields.Boolean(string="董事会", store=True)
boss_date = fields.Datetime(string=' ')
boss_char = fields.Char(string=' ')
is_note = fields.Boolean(string="上级单位", compute='_compute_issue_library_line_ids', store=True)
is_district_state = fields.Boolean(string="区国资局", compute='_compute_issue_library_line_ids', store=True)
is_district_government = fields.Boolean(string="区政府", compute='_compute_issue_library_line_ids', store=True)
@@ -64,7 +65,9 @@ class IssueLibrary(models.Model):
member_of_department = fields.Boolean("是否是本部门数据", compute='_compute_part_of_department',
search='_search_part_of_department')
is_passed = fields.Selection([('',''), ('pass', '通过'), ('reject', '未通过')])
is_party_passed = fields.Selection([('',''), ('pass', '通过'), ('reject', '未通过')])
is_class_passed = fields.Selection([('',''), ('pass', '通过'), ('reject', '未通过')])
is_boss_passed = fields.Selection([('',''), ('pass', '通过'), ('reject', '未通过')])
is_push_party = fields.Boolean(string="党推送")
is_push_class = fields.Boolean(string="班子推送")
is_push_boss = fields.Boolean(string="董事推送")
@@ -95,21 +98,21 @@ class IssueLibrary(models.Model):
def library_push(self):
for rec in self:
if rec.is_passed != 'pass':
raise UserError("当前状态不允许推送")
push_update_vals = {}
if rec.is_party and rec.is_passed:
if rec.is_party and rec.is_party_passed == 'reject' or not rec.is_party_passed:
push_update_vals['is_push_party'] = True
if rec.is_class and rec.is_passed:
elif rec.is_class and rec.is_class_passed == 'reject' or not rec.is_class_passed:
push_update_vals['is_push_class'] = True
if rec.is_boss and rec.is_passed:
elif rec.is_boss and rec.is_boss_passed == 'reject' or not rec.is_boss_passed:
push_update_vals['is_push_boss'] = True
if push_update_vals:
rec.write(push_update_vals)
rec.write({
'issue_state': 'will'
})
@api.onchange('is_legal_trial')
def _onchange_is_legal_trial(self):
@@ -294,8 +297,6 @@ class IssueLibrary(models.Model):
# for record in issue_active_ids:
# if record.state != 'will':
# raise UserError("当前状态不允许上会")
for meet in self:
meet.write({"issue_state": "on_meet"})
issue_details = []
for issue in issue_active_ids:
issue_details.append((0, 0, {
@@ -309,6 +310,7 @@ class IssueLibrary(models.Model):
"views": [[form_id, "form"]],
"context": {
'default_is_issue': True,
'default_start_datetime': fields.Datetime.now() + timedelta(hours=8),
'default_issue_meeting_line_ids': issue_details,
}
}
+1 -1
View File
@@ -15,7 +15,7 @@
<field name="minutes_id" invisible="not minutes_id"/>
</xpath>
<xpath expr="//button[@name='sync_wechat_room']" position="after">
<button name="bottom_meeting_approval" string="会议纪要" type="object" class="oe_highlight"/>
<button name="bottom_meeting_approval" string="会议纪要" type="object" class="oe_highlight" invisible="not work_end"/>
</xpath>
<xpath expr="//page[@name='sign']" position="before">
+18 -13
View File
@@ -12,11 +12,11 @@
<field name="name_text" class="list_width"/>
<field name="is_party" groups="yuthon_salary.group_jtbb_user" string="党总支"/>
<field name="is_party" groups="yuthon_salary.group_lc_user,yuthon_salary.group_wg_user" string="党支委会"/>
<field name="party_date" />
<field name="party_char" />
<field name="is_class"/>
<field name="class_date"/>
<field name="class_char"/>
<field name="is_boss" groups="yuthon_salary.group_jtbb_user"/>
<field name="boss_date"/>
<field name="boss_char"/>
<field name="is_legal_trial"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
@@ -45,18 +45,18 @@
decoration-info="issue_state == 'will'"
decoration-success="issue_state == 'done'"
decoration-muted="issue_state == 'cancel'">
<header>
<button name="all_issue_pull" string="批量提交审批" type="object"/>
</header>
<!-- <header>-->
<!-- <button name="all_issue_pull" string="批量提交上会审批" type="object"/>-->
<!-- </header>-->
<field name="name" column_invisible="1"/>
<field name="name_text" class="list_width"/>
<field name="is_party" groups="yuthon_salary.group_jtbb_user" string="党总支"/>
<field name="is_party" groups="yuthon_salary.group_lc_user,yuthon_salary.group_wg_user" string="党支委会"/>
<field name="party_date" />
<field name="party_char" />
<field name="is_class"/>
<field name="class_date"/>
<field name="class_char"/>
<field name="is_boss" groups="yuthon_salary.group_jtbb_user"/>
<field name="boss_date"/>
<field name="boss_char"/>
<field name="is_legal_trial"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
@@ -67,7 +67,7 @@
<field name="creart_datetime" column_invisible="1"/>
<field name="member_of_department" column_invisible="1"/>
<field name="is_push_depart" column_invisible="1"/>
<button name="single_issue_pull" string="提交" type="object" class="oe_highlight"/>
<!-- <button name="single_issue_pull" string="提交" type="object" class="oe_highlight"/>-->
<button name="library_push" string="推送" type="object" class="oe_highlight"/>
<button name="revoke" string="撤销" type="object" class="oe_highlight" invisible="issue_state == 'draft'"/>
<button name="issue_cancel" string="取消" type="object" class="oe_highlight"/>
@@ -206,6 +206,7 @@
<field name="model">issue.library</field>
<field name="arch" type="xml">
<search string="议题">
<field name="name"/>
<filter string="待上会" name="issue_state" domain="[('issue_state', '=', 'draft')]"/>
<filter string="已上会" name="issue_state" domain="[('issue_state', '=', 'on_meet')]"/>
<group expand="0">
@@ -219,6 +220,10 @@
<filter string="已取消" name="state_cen" domain="[('issue_state', '=', 'cancel')]"/>
</quick_filter>
</header_search>
<header_search class="header-search-inline">
<field name="name"/>
<field name="department_id"/>
</header_search>
</search>
</field>
</record>
@@ -251,7 +256,7 @@
<field name="creart_datetime"/>
<field name="is_party" groups="yuthon_salary.group_jtbb_user" string="党总支"/>
<field name="is_party" groups="yuthon_salary.group_lc_user,yuthon_salary.group_wg_user" string="党支委会"/>
<field name="party_date" column_invisibl="not party_date"/>
<field name="party_char"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
<field name="is_district_party"/>
@@ -407,7 +412,7 @@
<field name="code"/>
<field name="creart_datetime"/>
<field name="is_class"/>
<field name="class_date" column_invisibl="not class_date"/>
<field name="class_char"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
<field name="is_district_party"/>
@@ -551,7 +556,7 @@
<field name="code"/>
<field name="creart_datetime"/>
<field name="is_boss" groups="yuthon_salary.group_jtbb_user"/>
<field name="boss_date" column_invisibl="not boss_date"/>
<field name="boss_char"/>
<field name="is_district_state"/>
<field name="is_district_government"/>
<field name="is_district_party"/>
+1 -1
View File
@@ -22,7 +22,7 @@
sequence="5"/>
<menuitem id="tianhe_issue_library_menu"
parent="tianhe_issue_library_menu_root"
name="议题库"
name="议题总库"
groups="yuthon_issue.group_issue_admin"
action="issue_library_action"
sequence="10"/>