From acdc79a37b824bb7bc20cfa27d29c501fec3ebe6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
<9664676+pengyuthon@user.noreply.gitee.com>
Date: Sun, 22 Feb 2026 17:29:14 +0800
Subject: [PATCH 1/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../models/soong_workflow_record.py | 5 +-
.../views/soong_workflow_record.xml | 1 -
.../wizard/soong_workflow_comment_wizard.py | 1 -
yuthon_holiday/models/inherit_hr_leave.py | 26 ++++--
.../models/yuthon_vacation_reports.py | 1 +
yuthon_issue/__manifest__.py | 1 +
yuthon_issue/data/notice_room.xml | 18 +++++
yuthon_issue/models/inherit_room_booking.py | 81 +++++++++++++++++++
yuthon_issue/models/issue_library.py | 17 +++-
yuthon_notice/models/yuthon_notice.py | 2 +-
yuthon_official/models/yuthon_official.py | 33 ++++++++
.../models/yuthon_official_incoming_normal.py | 18 ++++-
.../yuthon_official_incoming_normal_views.xml | 8 +-
.../views/yuthon_official_views.xml | 8 +-
.../models/property_meter_archives.py | 2 +-
.../models/property_meter_water.py | 2 +-
.../models/property_lease_contract.py | 28 ++++---
17 files changed, 218 insertions(+), 34 deletions(-)
create mode 100644 yuthon_issue/data/notice_room.xml
diff --git a/soong_workflow/models/soong_workflow_record.py b/soong_workflow/models/soong_workflow_record.py
index e830c5f12..6a6cba87c 100644
--- a/soong_workflow/models/soong_workflow_record.py
+++ b/soong_workflow/models/soong_workflow_record.py
@@ -297,7 +297,10 @@ class SoongWorkflowRecord(models.Model):
valid_employee_ids = self.env['hr.employee'].search([
('department_id', 'in', department_ids)
]).ids
- domain = [('employee_ids', 'in', valid_employee_ids)]
+ valid_user_ids = self.env['res.users'].search([
+ ('employee_id', 'in', valid_employee_ids)
+ ]).ids
+ domain = [('users_ids', 'in', valid_user_ids)]
if (operator == '=' and not value) or (operator == '!=' and value):
domain = ['!'] + domain
return domain
diff --git a/soong_workflow/views/soong_workflow_record.xml b/soong_workflow/views/soong_workflow_record.xml
index 55d699de8..626c77f59 100644
--- a/soong_workflow/views/soong_workflow_record.xml
+++ b/soong_workflow/views/soong_workflow_record.xml
@@ -490,7 +490,6 @@
soong.workflow.record
tree,form,search
{'default_order': 'display_end_time desc,id desc'}
-
[('reclaim_state', '=', False), ('is_company', '=', True)]
diff --git a/soong_workflow/wizard/soong_workflow_comment_wizard.py b/soong_workflow/wizard/soong_workflow_comment_wizard.py
index 9d3528a87..2d49f17cd 100644
--- a/soong_workflow/wizard/soong_workflow_comment_wizard.py
+++ b/soong_workflow/wizard/soong_workflow_comment_wizard.py
@@ -582,7 +582,6 @@ class SoongWorkflowCommentWizard(models.TransientModel):
user_ids = [employee.user_id.id for employee in self.employee_ids if employee.user_id]
if hasattr(related_record, 'self_users_ids'):
related_record.self_users_ids = [(6, 0, user_ids)] if user_ids else [(5,)]
- workflow_record_id.users_ids = [(6, 0, user_ids)]
if self.node_btn_name in ['结束', '确认']:
is_work_remind = False
current_user = self.env.user
diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py
index 9453b0ae6..22f93a087 100644
--- a/yuthon_holiday/models/inherit_hr_leave.py
+++ b/yuthon_holiday/models/inherit_hr_leave.py
@@ -600,14 +600,24 @@ class HolidaysRequestBatch(models.Model):
})
actual_vacation_report.update_line()
actual_vacation_report._compute_working_overtime()
- domain = [
- ('employee_id', '=', self.users_id.employee_ids.id),
- ('is_visible', '=', False),
- ('is_void', '=', False),
- ('text', '!=', '上月额度继承'),
- ]
- if self.users_id.employee_ids.company_id.name == '集团本部':
- domain.append(('start_time', '>=', datetime(2025, 12, 1)),)
+ if self.env.user.company_id.name == '集团本部':
+ domain = [
+ ('employee_id.name', '=', '吴欣欣'),
+ '|',
+ ('is_initial_number', '=', True),
+ '&', ('is_initial_number', '=', False),
+ '&', ('update_type', '=', 'add'),
+ '&', ('workflow_record_id', '!=', False),
+ '&', ('start_time', '>=', datetime(2025, 12, 31, 23, 59, 59)),
+ '&', ('vacation_id', '!=', False),
+ ('employee_id.is_annual_salary', '!=', True)
+ ]
+ else:
+ domain = [
+ ('workflow_record_id', '!=', False),
+ ('update_type', '=', 'add'),
+ ('remaining_quantity', '!=', 0.0),
+ ]
overtime_records = self.env['vacation.over.line'].search(domain, order='start_time ASC')
remaining_deduct = hours
for record in overtime_records:
diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
index 897dc7d2c..5180475af 100644
--- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py
+++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
@@ -1614,6 +1614,7 @@ class YuthonVacationReports(models.Model):
# 原有domain(先筛选出基础数据)
if self.env.user.company_id.name == '集团本部':
domain = [
+ ('employee_id.name', '=', '吴欣欣'),
'|',
('is_initial_number', '=', True),
'&', ('is_initial_number', '=', False),
diff --git a/yuthon_issue/__manifest__.py b/yuthon_issue/__manifest__.py
index ce88d93b8..b37a66d52 100644
--- a/yuthon_issue/__manifest__.py
+++ b/yuthon_issue/__manifest__.py
@@ -15,6 +15,7 @@
'security/issue_security_group.xml',
'security/ir.model.access.csv',
'data/issue_library_code_data.xml',
+ 'data/notice_room.xml',
'views/issue_library_views.xml',
'views/issue_type_views.xml',
'views/issue_approval_views.xml',
diff --git a/yuthon_issue/data/notice_room.xml b/yuthon_issue/data/notice_room.xml
new file mode 100644
index 000000000..348328a5d
--- /dev/null
+++ b/yuthon_issue/data/notice_room.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ 定时任务:会议通知
+
+ code
+ model.notice_room()
+
+
+ 1
+ minutes
+
+ -1
+
+
+
+
diff --git a/yuthon_issue/models/inherit_room_booking.py b/yuthon_issue/models/inherit_room_booking.py
index 9fdee03fb..b50ac52be 100644
--- a/yuthon_issue/models/inherit_room_booking.py
+++ b/yuthon_issue/models/inherit_room_booking.py
@@ -1,6 +1,7 @@
from odoo import api, fields, models, _
from odoo.exceptions import UserError
from datetime import timedelta
+from datetime import datetime, timedelta, date
class RoomBooking(models.Model):
@@ -52,6 +53,86 @@ class RoomBooking(models.Model):
else:
return [('company_id', 'not in', company_ids)]
+ def notice_room(self):
+ booking_records = self.search([
+ ('work_end', '=', True),
+ ('task_time_ids', '!=', False),
+ ('start_datetime', '!=', False)
+ ])
+ if not booking_records:
+ return
+
+ now = fields.Datetime.now()
+ for booking in booking_records:
+ remind_time_thresholds = []
+ for task_time in booking.task_time_ids:
+ if not task_time.number or task_time.number <= 0:
+ continue
+ if task_time.task_unit == 'min':
+ threshold = timedelta(minutes=task_time.number)
+ elif task_time.task_unit == 'hour':
+ threshold = timedelta(hours=task_time.number)
+ elif task_time.task_unit == 'day':
+ threshold = timedelta(days=task_time.number)
+ else:
+ continue
+ remind_time_thresholds.append(threshold)
+
+ if not remind_time_thresholds:
+ continue
+
+ meeting_start = fields.Datetime.to_datetime(booking.start_datetime) + timedelta(hours=8)
+ actual_diff = meeting_start - now
+ if actual_diff.total_seconds() < 0:
+ continue
+
+ need_remind = False
+ for threshold in remind_time_thresholds:
+ diff_seconds = abs(actual_diff.total_seconds() - threshold.total_seconds())
+ if diff_seconds <= 60: # 误差≤60秒即判定为匹配
+ need_remind = True
+ break
+
+ if need_remind:
+ remind_users = self._get_remind_users(booking)
+ employee_ids = self.env['hr.employee'].search([('user_id', 'in', remind_users.ids)])
+ url = f'https://oa.thtzjt.com/web#id={self.id}&menu_id=9539&action=811&model=room.booking&view_type=form'
+ data = self.name + '\n'
+ time_str = fields.Datetime.to_datetime(self.start_datetime + timedelta(hours=8)).strftime('%H:%M:%S')
+ data += f'会议时间:{time_str}'
+ for employee_id in employee_ids:
+ self.sudo().env["wecom.apps"].sync_send_message_textcard(category='task',
+ user_id=employee_id.wecom_userid,
+ title="会议通知", description=data,
+ url=url, btntxt="详细信息")
+
+ def _get_remind_users(self, booking):
+ remind_user_ids = set()
+ # ① 组织者
+ if booking.organizer_id:
+ remind_user_ids.add(booking.organizer_id.id)
+ # ② 记录人
+ if booking.user_id:
+ remind_user_ids.add(booking.user_id.id)
+ # ③ 出席人员
+ if booking.room_users_ids:
+ remind_user_ids.update(booking.room_users_ids.ids)
+ # ④ 传达人
+ for line in booking.issue_meeting_line_ids:
+ if line.employee_id and line.employee_id.user_id:
+ remind_user_ids.add(line.employee_id.user_id.id)
+ # ⑤ 列席人员
+ for line in booking.issue_meeting_line_ids:
+ if line.attendees_ids:
+ for employee in line.attendees_ids:
+ remind_user_ids.add(employee.user_id.id)
+
+ # ⑥ 主持人
+ if booking.host and booking.host.user_id:
+ remind_user_ids.add(booking.host.user_id.id)
+ return self.env['res.users'].browse(list(remind_user_ids)).filtered(lambda u: u.active)
+
+
@api.depends('number_of_meetings', 'meeting_type_id', 'start_datetime')
def _compute_name(self):
if self.meeting_type_id and self.start_datetime:
diff --git a/yuthon_issue/models/issue_library.py b/yuthon_issue/models/issue_library.py
index be662b1a5..6210c767e 100644
--- a/yuthon_issue/models/issue_library.py
+++ b/yuthon_issue/models/issue_library.py
@@ -576,15 +576,30 @@ class IssueLibrary(models.Model):
('department_id', 'in', all_dept_ids),
('active', '=', True)
])
+ target_dept_names = ['办公室', '财务部', '风控法务部', '纪检审计部', '综合办公室']
+ target_managers = self.env['hr.employee'].search([
+ ('company_id', '=', current_user.company_id.id),
+ ('department_id.name', 'in', target_dept_names),
+ ('user_role_id.name', '=', '部门经理'),
+ ('active', '=', True)
+ ])
+
manager_employee = self.env['hr.employee'].search([
- ('id', 'in', target_employees.ids),
+ ('id', 'in', target_managers.ids),
('user_role_id.name', '=', '部门经理')
], limit=1)
+
+ attendees_ids = []
+ if target_managers and manager_employee:
+ attendees_ids = target_managers.filtered(lambda emp: emp.id != manager_employee.id).ids
+ elif target_managers:
+ attendees_ids = target_managers.id
for issue in self:
issue_details.append((0, 0, {
'issue_library_id': issue.id,
'employee_id': manager_employee.id,
'designated_person_ids': [(6, 0, target_employees.ids)],
+ 'attendees_ids': [(6, 0, attendees_ids)],
}))
if target_meeting_type_id:
continue
diff --git a/yuthon_notice/models/yuthon_notice.py b/yuthon_notice/models/yuthon_notice.py
index 16f830e25..d13927e3b 100644
--- a/yuthon_notice/models/yuthon_notice.py
+++ b/yuthon_notice/models/yuthon_notice.py
@@ -221,7 +221,7 @@ class YuthonNotice(models.Model):
msg_id = False
touser = "|".join(we_id_list) if we_id_list else ""
if touser:
- url = f'http://oa.thtzjt.com/webh5#/notice?id={self.id}'
+ url = f'https://phone.thtzjt.com/phone/Notice/110?employee_id='
result_data = self.sudo().env["wecom.apps"].sync_send_message_textcard(
category='notice',
touser=touser,
diff --git a/yuthon_official/models/yuthon_official.py b/yuthon_official/models/yuthon_official.py
index 5c9012bf8..cee50ac44 100644
--- a/yuthon_official/models/yuthon_official.py
+++ b/yuthon_official/models/yuthon_official.py
@@ -66,6 +66,8 @@ class YuthonOfficial(models.Model):
deadline = fields.Datetime(string="办理期限")
done_date = fields.Date(string="完成时间")
workflow_inspector_ids = fields.One2many('soong.workflow.inspector', 'yuthon_official_id', string="督办事项")
+ approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")], string="审批状态",
+ compute='_compute_workflow', index=True)
room_booking_ids = fields.One2many('room.booking', 'yuthon_official_id', string="会议关联")
issue_library_ids = fields.One2many('issue.library', 'yuthon_official_id', string="议题关联")
work_end = fields.Boolean(string="结束")
@@ -77,6 +79,37 @@ class YuthonOfficial(models.Model):
name_title = fields.Char(default=_get_default_name_title)
self_users_ids = fields.Many2many('res.users', string="当前经办人", default=lambda self: [(6, 0, [self.env.user.id])])
+ is_company = fields.Boolean(string="本公司", default=True, compute='_compute_is_company',
+ search='_search_part_of_company')
+
+ @api.depends('code')
+ def _compute_workflow(self):
+ for record in self:
+ record.approve_state = False
+ workflow = self.env['soong.workflow.record'].sudo().search(
+ [('code', '=', record.code)],
+ limit=1
+ )
+ if workflow:
+ record.approve_state = workflow.approve_state
+
+ @api.depends_context('uid', 'company')
+ @api.depends('company_id')
+ def _compute_is_company(self):
+ active_company_ids = self.env.companies
+ for employee in self:
+ employee.is_company = employee.company_id in active_company_ids
+
+ def _search_part_of_company(self, operator, value):
+ company_ids = self.env.companies.ids # 获取公司ID列表
+ # 处理反向操作符
+ if not value:
+ operator = '!=' if operator == '=' else '='
+ # 使用company_id in company_ids的结构
+ if operator == '=':
+ return [('company_id', 'in', company_ids)]
+ else: # operator == '!='
+ return [('company_id', 'not in', company_ids)]
@api.depends('name')
def _compute_text(self):
diff --git a/yuthon_official/models/yuthon_official_incoming_normal.py b/yuthon_official/models/yuthon_official_incoming_normal.py
index 2ec3c83fe..1055e4ebb 100644
--- a/yuthon_official/models/yuthon_official_incoming_normal.py
+++ b/yuthon_official/models/yuthon_official_incoming_normal.py
@@ -20,6 +20,7 @@ class YuthonOfficialIncomingNormal(models.Model):
main_text = fields.Text(string='正文')
come_file_id = fields.Char(string="来文编号")
incoming_file_id = fields.Char(string="收文编号")
+ approve_state = fields.Selection([("start", "草稿"), ("pending", "审批中"), ("end", "结束")], string="审批状态", compute='_compute_workflow', index=True)
urgency_level = fields.Selection([('normal', '一般'), ('urgent', '紧急'), ('extra_urgent', '特急')], string="紧急程度", default='normal')
secret_level = fields.Selection([('normal', '一般'), ('urgent', '秘密'), ('secret', '机密'), ('so_secret', '绝密')], string="密级")
time_date = fields.Datetime(string="日期", default=fields.Datetime.now)
@@ -45,7 +46,8 @@ class YuthonOfficialIncomingNormal(models.Model):
room_booking_ids = fields.One2many('room.booking', 'incoming_normal_id', string="会议关联")
issue_library_ids = fields.One2many('issue.library', 'incoming_normal_id', string="议题关联")
related_issues_id = fields.Many2one('issue.library', string="关联议题")
-
+ is_void = fields.Boolean(string="作废", compute='_compute_workflow', store=True)
+
# 显示用的办结时间(如果有审批日志,取最后一条日志的时间;否则使用write_date)
display_end_time = fields.Datetime(
string="办结时间",
@@ -103,6 +105,20 @@ class YuthonOfficialIncomingNormal(models.Model):
else:
return [('company_id', 'not in', company_ids)]
+ @api.depends('code')
+ def _compute_workflow(self):
+ for record in self:
+ record.approve_state = False
+ record.is_void = False
+ workflow = self.env['soong.workflow.record'].sudo().search(
+ [('code', '=', record.code)],
+ limit=1
+ )
+ if workflow:
+ record.approve_state = workflow.approve_state
+ if workflow.reclaim_state == True:
+ record.is_void = True
+
@api.onchange('is_issues')
def _onchange_is_issues(self):
if self.is_issues:
diff --git a/yuthon_official/views/yuthon_official_incoming_normal_views.xml b/yuthon_official/views/yuthon_official_incoming_normal_views.xml
index 5e27a83df..4fcbcac10 100644
--- a/yuthon_official/views/yuthon_official_incoming_normal_views.xml
+++ b/yuthon_official/views/yuthon_official_incoming_normal_views.xml
@@ -9,10 +9,12 @@
-
-
+
+
+
@@ -104,7 +106,7 @@
行政收文
yuthon.official.incoming.normal
- [('is_company','=',True)]
+ [('is_company','=',True), ('is_void','=',False)]
tree,form
\ No newline at end of file
diff --git a/yuthon_official/views/yuthon_official_views.xml b/yuthon_official/views/yuthon_official_views.xml
index b0f8bd899..ba08395dd 100644
--- a/yuthon_official/views/yuthon_official_views.xml
+++ b/yuthon_official/views/yuthon_official_views.xml
@@ -9,11 +9,13 @@
-
+
-
+
+
@@ -69,6 +71,7 @@
+
@@ -127,6 +130,7 @@
行政发文
yuthon.official
+ [('is_company','=',True)]
tree,form
diff --git a/yuthon_property/models/property_meter_archives.py b/yuthon_property/models/property_meter_archives.py
index ced0f9340..1c8e5e951 100644
--- a/yuthon_property/models/property_meter_archives.py
+++ b/yuthon_property/models/property_meter_archives.py
@@ -270,7 +270,7 @@ class MeterArchivesLine(models.Model):
type = fields.Selection([("public", "公共"), ("fees", "收费")], string="类型")
employee_ids = fields.Many2many("hr.employee", string="抄表人")
remarks = fields.Text(string="备注")
- is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="yes", string="是否固定线损")
+ is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价")
archives_state = fields.Selection([("stop", "停用"), ("use", "正常"), ("rent", "出租")], string="状态", default="use",)
state = fields.Selection([("reading", "已抄"), ("unreading", "未抄")], string="抄表状态", default="unreading",)
diff --git a/yuthon_property/models/property_meter_water.py b/yuthon_property/models/property_meter_water.py
index b89ac5e77..9f66eb465 100644
--- a/yuthon_property/models/property_meter_water.py
+++ b/yuthon_property/models/property_meter_water.py
@@ -236,7 +236,7 @@ class MeterWaterLine(models.Model):
remarks = fields.Text(string='备注')
employee_ids = fields.Many2many('hr.employee', string='抄表人')
maximum_count = fields.Integer(string='最大位数')
- is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="yes", string="是否固定线损")
+ is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价")
is_push = fields.Boolean(string='推送', default=False)
diff --git a/yuthon_property_approval/models/property_lease_contract.py b/yuthon_property_approval/models/property_lease_contract.py
index 19445b586..b205cfcb7 100644
--- a/yuthon_property_approval/models/property_lease_contract.py
+++ b/yuthon_property_approval/models/property_lease_contract.py
@@ -651,13 +651,9 @@ class PropertyLeaseContract(models.Model):
if rec.electricity_fee_ids:
for line in rec.electricity_fee_ids:
line.is_fix_loss = 'no'
- if line.unit_price > 0:
- line.is_fix_price = 'no'
if rec.water_fee_ids:
for line in rec.water_fee_ids:
line.is_fix_loss = 'no'
- if line.unit_price > 0:
- line.is_fix_price = 'yes'
def update_admin_id(self):
contract_ids = self.env["property.lease.contract"].browse(
@@ -670,13 +666,11 @@ class PropertyLeaseContract(models.Model):
for fee in rec.water_fee_ids:
fee.line_loss = 0.02
-
@api.onchange("lease_ids")
def onchange_lease_ids(self):
for rec in self:
start = rec.term_start_time
end = rec.term_end_time
- # 初始化值
rec.is_water = False
rec.is_electricity = False
rec.water_fee_ids = [(5, 0, 0)]
@@ -685,18 +679,24 @@ class PropertyLeaseContract(models.Model):
water_fee_values = []
message_fee_values = []
electricity_fee_values = []
+
+ # 用于去重的ID集合:记录已添加的水表/电表ID
+ added_water_meter_ids = set()
+ added_electric_meter_ids = set()
+
if rec.lease_ids:
# 处理每个租赁记录
for lease in rec.lease_ids:
if lease.address_id:
- # 处理水表
+ # 处理水表:增加去重逻辑
if lease.address_id.is_meter and lease.address_id.water_meter_ids:
active_water_meters = lease.address_id.water_meter_ids.filtered(
lambda m: not m.demolition_date or m.demolition_date > fields.Date.today()
)
for meter in active_water_meters:
- # 确保meter记录有效
- if meter.property_meter_water_id:
+ if meter.property_meter_water_id and meter.property_meter_water_id.id not in added_water_meter_ids:
+ # 记录已添加的水表ID,防止重复
+ added_water_meter_ids.add(meter.property_meter_water_id.id)
water_fee_values.append([0, 0, {
'contract_id': rec.id,
'property_meter_water_id': meter.property_meter_water_id.id,
@@ -708,8 +708,10 @@ class PropertyLeaseContract(models.Model):
lambda m: not m.demolition_date or m.demolition_date > fields.Date.today()
)
for meter in active_electric_meters:
- # 确保meter记录有效
- if meter.property_meter_archives_id:
+ # 确保meter记录有效 + 未被添加过
+ if meter.property_meter_archives_id and meter.property_meter_archives_id.id not in added_electric_meter_ids:
+ # 记录已添加的电表ID,防止重复
+ added_electric_meter_ids.add(meter.property_meter_archives_id.id)
electricity_fee_values.append([0, 0, {
'contract_id': rec.id,
'property_meter_archives_id': meter.property_meter_archives_id.id,
@@ -2472,7 +2474,7 @@ class PropertyWaterFeeLine(models.Model):
end_date = fields.Date(related='contract_id.term_end_time', string="结束日期")
unit_price = fields.Float(string='固定单价', digits=(16, 4))
line_loss = fields.Float(string='固定线损', digits=(16, 4))
- is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="yes", string="是否固定线损")
+ is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价")
epitope_values_id = fields.Many2one(related='property_meter_water_id.epitope_values_id', string="表位置")
@@ -2499,7 +2501,7 @@ class PropertyElectricFeeLine(models.Model):
end_date = fields.Date(related='contract_id.term_end_time', string="结束日期")
unit_price = fields.Float(string='固定单价', digits=(16, 4))
line_loss = fields.Float(string='固定线损', digits=(16, 4))
- is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="yes", string="是否固定线损")
+ is_fix_loss = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定线损")
is_fix_price = fields.Selection([('yes', '是'),('no', '否')], default="no", string="是否固定单价")
epitope_values_id = fields.Many2one(related='property_meter_archives_id.epitope_values_id', string="表位置")
From 7f658e8e062c1c3baa5d81a8c99f45944008945b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
<9664676+pengyuthon@user.noreply.gitee.com>
Date: Sun, 22 Feb 2026 17:59:29 +0800
Subject: [PATCH 2/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
yuthon_hr_employee/models/yuthon_medical_manage.py | 9 ++++++++-
yuthon_hr_employee/models/yuthon_party_setting.py | 1 +
yuthon_hr_employee/views/yuthon_party_setting_views.xml | 1 +
3 files changed, 10 insertions(+), 1 deletion(-)
diff --git a/yuthon_hr_employee/models/yuthon_medical_manage.py b/yuthon_hr_employee/models/yuthon_medical_manage.py
index f950cc2a5..269dbea9c 100644
--- a/yuthon_hr_employee/models/yuthon_medical_manage.py
+++ b/yuthon_hr_employee/models/yuthon_medical_manage.py
@@ -109,9 +109,16 @@ class YuthonMedicalManage(models.Model):
current_date = fields.Date.today()
current_year = str(current_date.year)
last_year = str(int(current_year) - 1)
+ target_companies = ['广州市天河投资集团有限公司', '广州市天投城市建设开发有限公司',
+ '广州天投云擎数字科技有限公司']
+ party_setting_records = self.env['yuthon.party.setting'].search([
+ ('party', 'in', target_companies)
+ ]).ids
open_employees = self.env['hr.employee'].search([
('user_state', '=', 'in'),
- ('company_id.name', '=', '集团本部')
+ ('company_id.name', '=', '集团本部'),
+ ('contract_id.party_id', 'in', party_setting_records),
+ ('active', '=', True)
])
for employee in open_employees:
medical_manage_rec = self.env['yuthon.medical.manage'].search([
diff --git a/yuthon_hr_employee/models/yuthon_party_setting.py b/yuthon_hr_employee/models/yuthon_party_setting.py
index b693b66eb..6c054e4e5 100644
--- a/yuthon_hr_employee/models/yuthon_party_setting.py
+++ b/yuthon_hr_employee/models/yuthon_party_setting.py
@@ -16,6 +16,7 @@ class YuthonPartySetting(models.Model):
address = fields.Text(string='通讯地址')
contact = fields.Char(string='联系人')
phone = fields.Char(string='电话')
+ level = fields.Selection([('one', '一级'), ('two', '二级'), ('three', '三级')], string='层级')
effective_date = fields.Date(string="生效日期", default=fields.Date.today)
expiration_date = fields.Date(string="失效日期")
diff --git a/yuthon_hr_employee/views/yuthon_party_setting_views.xml b/yuthon_hr_employee/views/yuthon_party_setting_views.xml
index 109e1e9aa..2bf714677 100644
--- a/yuthon_hr_employee/views/yuthon_party_setting_views.xml
+++ b/yuthon_hr_employee/views/yuthon_party_setting_views.xml
@@ -13,6 +13,7 @@
+
From 63fec9502ee6dd42afa18c6bd6b65a7005f48b2d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
<9664676+pengyuthon@user.noreply.gitee.com>
Date: Sun, 22 Feb 2026 18:33:54 +0800
Subject: [PATCH 3/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../models/yuthon_vacation_reports.py | 25 +++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
diff --git a/yuthon_hr_attendance/models/yuthon_vacation_reports.py b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
index 5180475af..f5215e3f9 100644
--- a/yuthon_hr_attendance/models/yuthon_vacation_reports.py
+++ b/yuthon_hr_attendance/models/yuthon_vacation_reports.py
@@ -720,6 +720,7 @@ class YuthonVacationReports(models.Model):
probation_days = 0 # 试用期天数
regular_days = 0 # 转正天数
actual_attendance = 0 # 实到天数 = 应到天数 - 请假总天数 - 异常天数
+ leave_total_days = 0 # 累计请假天数
is_retired = rec.employee_id.user_state in ('leave', 'withdraw')
if rec.employee_id and rec.vacation_date and not is_retired:
year = rec.vacation_date.year
@@ -740,15 +741,35 @@ class YuthonVacationReports(models.Model):
])
if attendances:
+ # 1. 过滤有效考勤:仅排除休息/异常,保留请假记录
valid_attendances = [
att for att in attendances
if (att.check_in or att.check_out)
and att.shift_name != '休息'
- and (not att.workflow_id or att.workflow_id.workflow_id.name != '请假')
and not att.is_abnormal
]
+ leave_hours = 0
+ leave_days = 0
+ processed_leave_codes = set() # 记录已处理的请假单code,避免重复扣减
+
+ for att in attendances:
+ if att.workflow_id and att.workflow_id.workflow_id.name == '请假':
+ leave_record = self.env[att.workflow_id.res_model].sudo().browse(att.workflow_id.res_id)
+ if leave_record.code and leave_record.code in processed_leave_codes:
+ continue
+
+ if leave_record.days:
+ leave_days += leave_record.days
+ if leave_record.code:
+ processed_leave_codes.add(leave_record.code)
+
+ total_leave_days = leave_days
+ leave_total_days += leave_days
if valid_attendances:
- actual_attendance += 1
+ daily_actual = max(1 - total_leave_days, 0)
+ actual_attendance += daily_actual
+
+ rec.actual_attendance = max(actual_attendance, 0)
for att in attendances:
if att.is_late_card:
From 702a614636794ea394c53e7272e71157f69161a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
<9664676+pengyuthon@user.noreply.gitee.com>
Date: Sun, 22 Feb 2026 18:54:42 +0800
Subject: [PATCH 4/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
soong_workflow/wizard/soong_workflow_comment_wizard.py | 2 +-
.../views/yuthon_message_login_views.xml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/soong_workflow/wizard/soong_workflow_comment_wizard.py b/soong_workflow/wizard/soong_workflow_comment_wizard.py
index 2d49f17cd..8b02252da 100644
--- a/soong_workflow/wizard/soong_workflow_comment_wizard.py
+++ b/soong_workflow/wizard/soong_workflow_comment_wizard.py
@@ -751,7 +751,7 @@ class SoongWorkflowCommentWizard(models.TransientModel):
description=f"标题为{record_id.name}的{record_id._description}已结束,请归档",
url=url, btntxt="查看详情")
- # self.node_btn_id.with_context(cx).run()
+ self.node_btn_id.with_context(cx).run()
if self.node_btn_name == "分发":
record_id.is_send = True
menu_id = self.env['ir.ui.menu'].search([('name', '=', '行政收文')], limit=1)
diff --git a/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml b/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml
index e0307fbe4..b60a21d32 100644
--- a/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml
+++ b/yuthon_hr_employee_approval/views/yuthon_message_login_views.xml
@@ -6,7 +6,7 @@
From 885e518038df9eeb4aacba229a97a28b68d835de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
Date: Sun, 22 Feb 2026 19:46:07 +0800
Subject: [PATCH 5/6] =?UTF-8?q?=E6=89=BF=E7=A7=9F=E6=96=B9=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF=E5=AF=BC=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
yuthon_property/__manifest__.py | 1 +
yuthon_property/security/ir.model.access.csv | 3 +-
yuthon_property/wizard/__init__.py | 1 +
.../yuthon_report/receivable_report.js | 47 ++++++++++++++++++-
4 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/yuthon_property/__manifest__.py b/yuthon_property/__manifest__.py
index 5fb268817..0205354e8 100644
--- a/yuthon_property/__manifest__.py
+++ b/yuthon_property/__manifest__.py
@@ -70,6 +70,7 @@
'wizard/meter_price_wizard.xml',
'wizard/property_statements_wizard_views.xml',
'wizard/utility_meter_wizard_views.xml',
+ 'wizard/tenant_import_wizard_views.xml',
'views/menuitem.xml',
],
'assets': {
diff --git a/yuthon_property/security/ir.model.access.csv b/yuthon_property/security/ir.model.access.csv
index 3f9f14f49..5e9850cf4 100644
--- a/yuthon_property/security/ir.model.access.csv
+++ b/yuthon_property/security/ir.model.access.csv
@@ -102,4 +102,5 @@ access_meter_water_line, meter.water.line,model_meter_water_line,base.group_user
access_yuthon_epitope_values, yuthon.epitope.values,model_yuthon_epitope_values,base.group_user,1,1,1,1
access_task_push_wizard,task.push.wizard,model_task_push_wizard,base.group_user,1,1,1,1
access_utility_meter_wizard,utility.meter.wizard,model_utility_meter_wizard,base.group_user,1,1,1,1
-access_invoicing_information_line,invoicing.information.line,model_invoicing_information_line,base.group_user,1,1,1,1
\ No newline at end of file
+access_invoicing_information_line,invoicing.information.line,model_invoicing_information_line,base.group_user,1,1,1,1
+access_tenant_import_wizard,tenant.import.wizard,model_tenant_import_wizard,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/yuthon_property/wizard/__init__.py b/yuthon_property/wizard/__init__.py
index 896063db9..2a5319f28 100644
--- a/yuthon_property/wizard/__init__.py
+++ b/yuthon_property/wizard/__init__.py
@@ -11,3 +11,4 @@ from . import initialize_wizard
from . import electric_water_wizard
from . import task_push_wizard
from . import utility_meter_wizard
+from . import tenant_import_wizard
diff --git a/yuthon_report/static/src/components/yuthon_report/receivable_report.js b/yuthon_report/static/src/components/yuthon_report/receivable_report.js
index ac112b13a..f933df4da 100644
--- a/yuthon_report/static/src/components/yuthon_report/receivable_report.js
+++ b/yuthon_report/static/src/components/yuthon_report/receivable_report.js
@@ -19,7 +19,7 @@ class ReceivableReport extends Component {
this.gridRef = useRef("grid");
console.log(this.props.action.context.report_config)
- this.serviceOrm = useService("orm")
+ this.serviceOrm = useService("orm") this.actionService = useService("action"); // 获取 actionService 用于跳转
// 计算 本月的开始,结束
@@ -46,10 +46,17 @@ class ReceivableReport extends Component {
onMounted(async () => {
// 加载 ag-grid
//await loadJS("https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js");
+
+ // 统一处理 columns:给所有列添加穿透渲染器
+ const columns = this.config.columns || [];
+ columns.forEach((col) => {
+ col.cellRenderer = this.linkRender.bind(this);
+ });
+
this.gridOptions = {
localeText: AG_GRID_LOCALE_CN,
theme: agGrid.themeMaterial,
- columnDefs: this.config.columns || [],
+ columnDefs: columns,
rowData: [],
rowClassRules: {
'ag-row-new-rent': function(params) {
@@ -102,6 +109,9 @@ class ReceivableReport extends Component {
items.push("chartRange");
return items;
},
+ context: {
+ actionService: this.actionService // 传递 actionService 用于穿透跳转
+ }
};
this.grid = new agGrid.createGrid(this.gridRef.el, this.gridOptions);
@@ -109,6 +119,39 @@ class ReceivableReport extends Component {
});
}
+ /**
+ * 穿透渲染器:将对象格式的字段渲染为可点击的链接
+ * 如果字段值是对象(包含 model、id、display_name),则渲染为可点击链接
+ * 否则直接显示原值
+ */
+ linkRender(params) {
+ if (!params.value) return '';
+
+ // 如果是对象格式(包含 model 和 id),渲染为可点击链接
+ if (typeof params.value === 'object' && params.value.model && params.value.id) {
+ const { model, id, display_name } = params.value;
+
+ const el = document.createElement('div');
+ el.className = 'o_record_cell';
+ el.innerText = display_name || '';
+ el.classList.add('btn');
+ el.classList.add('btn-link');
+ el.onclick = () => {
+ params.context.actionService.doAction({
+ type: 'ir.actions.act_window',
+ res_model: model,
+ res_id: id,
+ views: [[false, 'form']]
+ });
+ };
+
+ return el;
+ }
+
+ // 否则直接返回原值(纯文本/数字)
+ return params.value;
+ }
+
/**
* 改变布尔值
* @param e
From 62d6131b50b500b43e1fd2409c082985f9dc3410 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?=
Date: Sun, 22 Feb 2026 19:46:54 +0800
Subject: [PATCH 6/6] =?UTF-8?q?=E6=89=BF=E7=A7=9F=E6=96=B9=E4=BF=A1?=
=?UTF-8?q?=E6=81=AF=E5=AF=BC=E5=85=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../models/current_receivable_rent_report.py | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/yuthon_report/models/current_receivable_rent_report.py b/yuthon_report/models/current_receivable_rent_report.py
index e33e1998c..9dad81e8f 100644
--- a/yuthon_report/models/current_receivable_rent_report.py
+++ b/yuthon_report/models/current_receivable_rent_report.py
@@ -77,7 +77,11 @@ class CurrentReceivableRentReport(models.Model):
"序号": idx,
"应收开始": rec.due_date.strftime("%Y-%m-%d") if rec.due_date else "",
"应收结束": rec.due_date_end.strftime("%Y-%m-%d") if rec.due_date_end else "",
- "承租方名称": rec.lessee_id.name if rec.lessee_id else "",
+ "承租方名称": {
+ 'model': 'property.tenant.information',
+ 'id': rec.lessee_id.id,
+ 'display_name': rec.lessee_id.name,
+ } if rec.lessee_id else "",
"原始金额": round(amt, 2),
"减免金额": round(discount, 2) if discount else "",
"实际金额(含税)": round(actual_amt, 2),
@@ -85,8 +89,16 @@ class CurrentReceivableRentReport(models.Model):
"实际金额(不含税)": round(actual_amt_nt, 2),
"物业名称": rec.property_name or "",
"产权证": rec.title_deed or "",
- "合同编号": rec.contract_id.contract_code if rec.contract_id else "",
- "管理员": rec.admin_id.name if rec.admin_id else "",
+ "合同编号": {
+ 'model': 'property.lease.contract',
+ 'id': rec.contract_id.id,
+ 'display_name': rec.contract_id.contract_code,
+ } if rec.contract_id else "",
+ "管理员": {
+ 'model': 'res.users',
+ 'id': rec.admin_id.id,
+ 'display_name': rec.admin_id.name,
+ } if rec.admin_id else "",
"acc_type": rec.acc_type if rec.acc_type else False,
})