diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py
index 5a0eec397..47341ea8a 100644
--- a/yuthon_hr_attendance/models/hr_attendance.py
+++ b/yuthon_hr_attendance/models/hr_attendance.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from datetime import datetime, time
+from re import search
+
from odoo import models, fields, api, exceptions, _
from datetime import timedelta
from dateutil.relativedelta import relativedelta
@@ -111,27 +113,34 @@ class HrAttendance(models.Model):
# 判断是否为上月
record.is_last_month = (first_day_last_month <= record.current_data <= last_day_last_month)
- def compute_company_id(self):
- employee_date_records = {}
- for att in self.env["hr.attendance"].browse(self._context.get('active_ids', self._context.get('active_id'))):
- employee_id = att.employee_id.id
- # 创建唯一分组键 (员工ID, 日期)
- group_key = (employee_id, att.current_data)
- # 将记录按员工+日期分组
- if group_key not in employee_date_records:
- employee_date_records[group_key] = []
- employee_date_records[group_key].append(att.id)
+ def update_att(self):
+ # 定义要更新的员工姓名列表
+ target_employees = ["简梓豪", "林紫珊"]
+ # 定义日期范围:9月1日至9月15日
+ start_date = fields.Date.from_string("2025-09-01")
+ end_date = fields.Date.from_string("2025-09-15")
- # 处理重复记录:每个员工每天只保留第一条记录
- for (employee_id, date), record_ids in employee_date_records.items():
- if len(record_ids) > 1:
- # 保留第一条记录,其余标记为删除
- ids_to_keep = record_ids[0]
- ids_to_delete = record_ids[1:]
+ # 查找目标员工
+ employees = self.env["hr.employee"].search([
+ ("name", "in", target_employees)
+ ])
- # 执行删除操作
- if ids_to_delete:
- self.env["hr.attendance"].browse(ids_to_delete).unlink()
+ if not employees:
+ raise UserError("未找到指定的员工记录")
+
+ # 生成日期范围内的所有日期
+ current_date = start_date
+ while current_date <= end_date:
+ for employee in employees:
+ # 执行考勤详情计算方法
+ self.env["hr.attendance.detail"].compute_attendance_detail(
+ employee,
+ current_date,
+ is_manual=True
+ )
+ current_date = current_date + timedelta(days=1)
+
+ return True
diff --git a/yuthon_hr_employee/__pycache__/__init__.cpython-310.pyc b/yuthon_hr_employee/__pycache__/__init__.cpython-310.pyc
index 3fc365c69..ef3c61379 100644
Binary files a/yuthon_hr_employee/__pycache__/__init__.cpython-310.pyc and b/yuthon_hr_employee/__pycache__/__init__.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc
index 75e986c35..daa0f7af6 100644
Binary files a/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc and b/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/models/__pycache__/inherit_res_user.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/inherit_res_user.cpython-310.pyc
index e715083be..d9d0154c9 100644
Binary files a/yuthon_hr_employee/models/__pycache__/inherit_res_user.cpython-310.pyc and b/yuthon_hr_employee/models/__pycache__/inherit_res_user.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/models/__pycache__/yuthon_hr_department.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/yuthon_hr_department.cpython-310.pyc
index 58c528b40..48fdd907b 100644
Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_hr_department.cpython-310.pyc and b/yuthon_hr_employee/models/__pycache__/yuthon_hr_department.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/models/__pycache__/yuthon_hr_employee.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/yuthon_hr_employee.cpython-310.pyc
index 7e7db81db..2a46ff201 100644
Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_hr_employee.cpython-310.pyc and b/yuthon_hr_employee/models/__pycache__/yuthon_hr_employee.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-310.pyc
index e46a4ef23..562f22f80 100644
Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-310.pyc and b/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc b/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc
index 984dc39a1..cf19d77c5 100644
Binary files a/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc and b/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc differ
diff --git a/yuthon_hr_employee/wizard/__pycache__/hr_employee_wizard.cpython-310.pyc b/yuthon_hr_employee/wizard/__pycache__/hr_employee_wizard.cpython-310.pyc
index 348f5648d..749ec36c3 100644
Binary files a/yuthon_hr_employee/wizard/__pycache__/hr_employee_wizard.cpython-310.pyc and b/yuthon_hr_employee/wizard/__pycache__/hr_employee_wizard.cpython-310.pyc differ
diff --git a/yuthon_notice/__pycache__/__init__.cpython-310.pyc b/yuthon_notice/__pycache__/__init__.cpython-310.pyc
index 58e99d15d..ad5900f01 100644
Binary files a/yuthon_notice/__pycache__/__init__.cpython-310.pyc and b/yuthon_notice/__pycache__/__init__.cpython-310.pyc differ
diff --git a/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc b/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc
index 8de08b043..5d354a6a3 100644
Binary files a/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc and b/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc differ
diff --git a/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc b/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc
index b54979de3..5e03e4016 100644
Binary files a/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc and b/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc differ
diff --git a/yuthon_property/models/property_meter_archives.py b/yuthon_property/models/property_meter_archives.py
index be7de5f31..06609607a 100644
--- a/yuthon_property/models/property_meter_archives.py
+++ b/yuthon_property/models/property_meter_archives.py
@@ -17,7 +17,6 @@ class PropertyMeterArchives(models.Model):
lessee_id = fields.Many2one('property.tenant.information', string='承租方', store=True, compute='_compute_lessee_id')
employee_ids = fields.Many2many('hr.employee', string='抄表人')
epitope_values = fields.Char(string='表位置')
- meter_type = fields.Selection([('electric', '电表'), ('water', '水表'), ('smart', '智能电表')], string='仪表类型')
other = fields.Char(string='其他')
meter_date = fields.Date(string='任务日期')
last_dosage = fields.Float(string='上次用量')
diff --git a/yuthon_property/models/property_meter_water.py b/yuthon_property/models/property_meter_water.py
index e03663701..64a756739 100644
--- a/yuthon_property/models/property_meter_water.py
+++ b/yuthon_property/models/property_meter_water.py
@@ -15,7 +15,6 @@ class PropertyMeterWater(models.Model):
maximum_count = fields.Integer(string='最大位数')
lessee_id = fields.Many2one('property.tenant.information', store=True, compute='_compute_lessee_id', string="承租方")
employee_ids = fields.Many2many('hr.employee', string='抄表人')
- meter_type = fields.Selection([('electric', '电表'), ('water', '水表')], string='仪表类型')
epitope_values = fields.Char(string='表位置')
other = fields.Char(string='其他')
meter_date = fields.Date(string='任务日期')
diff --git a/yuthon_property/views/property_meter_archives_views.xml b/yuthon_property/views/property_meter_archives_views.xml
index 9dae3443a..3aca9d6af 100644
--- a/yuthon_property/views/property_meter_archives_views.xml
+++ b/yuthon_property/views/property_meter_archives_views.xml
@@ -58,7 +58,6 @@
-
@@ -133,6 +132,7 @@
电表库
property.meter.archives
tree,form,search
+ [('is_push', '=', False)]
diff --git a/yuthon_property/views/property_meter_water_views.xml b/yuthon_property/views/property_meter_water_views.xml
index 0ecb7e487..01f58d394 100644
--- a/yuthon_property/views/property_meter_water_views.xml
+++ b/yuthon_property/views/property_meter_water_views.xml
@@ -60,7 +60,6 @@
-
@@ -131,6 +130,7 @@
水表库
property.meter.water
tree,form,search
+ [('is_push', '=', False)]
diff --git a/yuthon_property/wizard/property_meter_wizard.py b/yuthon_property/wizard/property_meter_wizard.py
index 9f2e39dd1..094c6daa0 100644
--- a/yuthon_property/wizard/property_meter_wizard.py
+++ b/yuthon_property/wizard/property_meter_wizard.py
@@ -32,7 +32,6 @@ class PropertyMeterWizard(models.Model):
epitope_values = row[0].value
name = row[3].value
type_dict = {'水表': 'water', '电表': 'electric', '电表(电子表)': 'smart', '': ''}
- meter_type = type_dict.get(row[4].value)
unit_number = row[1].value
installation_location = row[2].value
if row[5].value:
@@ -63,7 +62,6 @@ class PropertyMeterWizard(models.Model):
'unit_number': unit_number,
'installation_location': installation_location,
'type':type,
- 'meter_type': meter_type,
'gauge_magnification': gauge_magnification,
'meter_date': meter_date,
'current_read': current_read,
diff --git a/yuthon_property/wizard/property_water_wizard.py b/yuthon_property/wizard/property_water_wizard.py
index 792dfe6a3..7dc981050 100644
--- a/yuthon_property/wizard/property_water_wizard.py
+++ b/yuthon_property/wizard/property_water_wizard.py
@@ -65,7 +65,6 @@ class PropertyWaterWizard(models.Model):
'unit_number': unit_number,
'installation_location': installation_location,
'type': type,
- 'meter_type': meter_type,
'gauge_magnification': gauge_magnification,
'meter_date': meter_date,
'current_read': current_read,
diff --git a/yuthon_web_notice/__init__.py b/yuthon_web_notice/__init__.py
new file mode 100644
index 000000000..3b3891601
--- /dev/null
+++ b/yuthon_web_notice/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+from . import models
+from . import controllers
diff --git a/yuthon_web_notice/__manifest__.py b/yuthon_web_notice/__manifest__.py
new file mode 100644
index 000000000..3707df115
--- /dev/null
+++ b/yuthon_web_notice/__manifest__.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+{
+ "name": "Yuthon Web Notice",
+ "summary": "通知公告模块",
+ "description": "通知公告模块",
+ "author": "Yuthon",
+ "website": "#",
+ "category": "Yuthon/Yuthon Web Notice",
+ "version": "17.0.0.1",
+ "depends": ["base", "web","hr"],
+ "data": [
+ "views/templates.xml",
+ ],
+ "assets": {
+ "yuthon_web_notice.assets_announcement": [
+ ("include", "web._assets_helpers"),
+ "web/static/src/scss/pre_variables.scss",
+ "web/static/lib/bootstrap/scss/_variables.scss",
+ ("include", "web._assets_bootstrap_backend"),
+ # 字体图标
+ "web/static/src/libs/fontawesome/css/font-awesome.css",
+ # include base files from framework
+ ("include", "web._assets_backend"),
+ ("include", "web._assets_core"),
+ "web/static/src/core/utils/functions.js",
+ "web/static/src/core/browser/browser.js",
+ "web/static/src/core/registry.js",
+ "web/static/src/core/assets.js",
+ "yuthon_web_notice/static/src/**/*",
+ ],
+ },
+ "installable": True,
+ "application": True,
+ "license": "AGPL-3",
+}
diff --git a/yuthon_web_notice/controllers/__init__.py b/yuthon_web_notice/controllers/__init__.py
new file mode 100644
index 000000000..457bae27e
--- /dev/null
+++ b/yuthon_web_notice/controllers/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+
+from . import controllers
\ No newline at end of file
diff --git a/yuthon_web_notice/controllers/controllers.py b/yuthon_web_notice/controllers/controllers.py
new file mode 100644
index 000000000..aa790aff0
--- /dev/null
+++ b/yuthon_web_notice/controllers/controllers.py
@@ -0,0 +1,10 @@
+from odoo import http
+from odoo.http import request, route
+
+class OwlPlayground(http.Controller):
+ @http.route(['/web_notice'], type='http', auth='public')
+ def show_playground(self):
+ """
+ Renders the owl playground page
+ """
+ return request.render('yuthon_web_notice.Announcement')
diff --git a/yuthon_web_notice/models/__init__.py b/yuthon_web_notice/models/__init__.py
new file mode 100644
index 000000000..d25661f9c
--- /dev/null
+++ b/yuthon_web_notice/models/__init__.py
@@ -0,0 +1 @@
+from . import hr_employee
\ No newline at end of file
diff --git a/yuthon_web_notice/models/hr_employee.py b/yuthon_web_notice/models/hr_employee.py
new file mode 100644
index 000000000..2646f1977
--- /dev/null
+++ b/yuthon_web_notice/models/hr_employee.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+from odoo import models, fields
+
+
+class HrEmployee(models.Model):
+ _inherit = "hr.employee"
+
+ def get_full_employee_info(self, employee_id):
+ """返回员工完整信息(所有字段)"""
+ employee = self.browse(employee_id)
+ if not employee.exists():
+ return {}
+
+ # 读取所有字段
+ fields = employee.fields_get().keys() # 获取所有字段名
+ return employee.sudo().read(fields)[0] # 返回字典
diff --git a/yuthon_web_notice/static/src/announcement.js b/yuthon_web_notice/static/src/announcement.js
new file mode 100644
index 000000000..427914cd9
--- /dev/null
+++ b/yuthon_web_notice/static/src/announcement.js
@@ -0,0 +1,32 @@
+/** @odoo-module **/
+
+import { Component, useState, onWillUnmount, } from "@odoo/owl";
+import { useService } from "@web/core/utils/hooks";
+
+export class Announcement extends Component {
+ static template = "yuthon_web_notice.Announcement";
+ static props = ["*"];
+ setup() {
+ console.log(useService);
+ debugger;
+ this.rpc = useService("rpc");
+ this.employee = useState({});
+ this.id = this.props.id || "";
+ onWillUnmount(async () => {
+ if (this.id) {
+ await this.loadEmployee(this.id);
+ }
+ });
+ }
+ async loadEmployee(employeeId) {
+ try {
+ this.employee = await this.rpc({
+ model: "hr.employee",
+ method: "get_full_employee_info",
+ args: [parseInt(employeeId)],
+ });
+ } catch (err) {
+ console.error("获取员工信息失败:", err);
+ }
+ }
+}
diff --git a/yuthon_web_notice/static/src/announcement.xml b/yuthon_web_notice/static/src/announcement.xml
new file mode 100644
index 000000000..efda3f4b0
--- /dev/null
+++ b/yuthon_web_notice/static/src/announcement.xml
@@ -0,0 +1,6 @@
+
+
+
+ Hello OWL Web
+
+
\ No newline at end of file
diff --git a/yuthon_web_notice/static/src/main.js b/yuthon_web_notice/static/src/main.js
new file mode 100644
index 000000000..ddfa7991d
--- /dev/null
+++ b/yuthon_web_notice/static/src/main.js
@@ -0,0 +1,51 @@
+/** @odoo-module **/
+
+import { browser } from "@web/core/browser/browser";
+import { makeEnv } from "@web/env"; // 引入环境构建工具
+import { mount, whenReady } from "@odoo/owl";
+import { Announcement } from "./announcement";
+import { templates } from "@web/core/assets";
+
+
+// 当document.body准备好时,挂载 Announcement 组件
+whenReady(() => {
+ const env = makeEnv(); // 创建标准 Odoo 环境
+ mount(Announcement, document.body, {
+ templates,
+ dev: true,
+ name: "Web Notice",
+ env,
+ props: {
+ id: new URLSearchParams(window.location.search).get("id"),
+ },
+ });
+});
+
+/**
+ * 错误日志捕获函数
+ * 遍历 error 对象的 cause 属性,输出错误堆栈信息(包含链式错误原因)
+ * @param {Event} ev 浏览器事件对象
+ */
+function logError(ev) {
+ ev.preventDefault();
+ let error = ev?.error || ev.reason;
+
+ if (error.seen) {
+ // 如果错误已经标记过 seen,就不再重复打印
+ // 场景:mount 失败时,Owl 会抛出错误,如果不处理就会打印两次
+ return;
+ }
+ error.seen = true;
+
+ let errorMessage = error.stack;
+ while (error.cause) {
+ errorMessage += "\nCaused by: "
+ errorMessage += error.cause.stack;
+ error = error.cause;
+ }
+ console.error(errorMessage);
+}
+// 监听全局错误事件(比如未捕获的异常)
+browser.addEventListener("error", (ev) => { logError(ev) });
+// 监听未捕获的 Promise 错误(unhandledrejection)
+browser.addEventListener("unhandledrejection", (ev) => { logError(ev) });
diff --git a/yuthon_web_notice/views/templates.xml b/yuthon_web_notice/views/templates.xml
new file mode 100644
index 000000000..1dc8800cf
--- /dev/null
+++ b/yuthon_web_notice/views/templates.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file