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