@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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='上次用量')
|
||||
|
||||
@@ -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='任务日期')
|
||||
|
||||
@@ -58,7 +58,6 @@
|
||||
<field name="lessee_id"/>
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
<field name="epitope_values"/>
|
||||
<field name="meter_type" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="other"/>
|
||||
@@ -133,6 +132,7 @@
|
||||
<field name="name">电表库</field>
|
||||
<field name="res_model">property.meter.archives</field>
|
||||
<field name="view_mode">tree,form,search</field>
|
||||
<field name="domain">[('is_push', '=', False)]</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property.view_property_meter_archives_tree')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property.property_meter_archives_form')})]"/>
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
<field name="lessee_id"/>
|
||||
<field name="employee_ids" widget="many2many_tags"/>
|
||||
<field name="epitope_values"/>
|
||||
<field name="meter_type" invisible="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="other"/>
|
||||
@@ -131,6 +130,7 @@
|
||||
<field name="name">水表库</field>
|
||||
<field name="res_model">property.meter.water</field>
|
||||
<field name="view_mode">tree,form,search</field>
|
||||
<field name="domain">[('is_push', '=', False)]</field>
|
||||
<field name="view_ids" eval="[(5, 0, 0),
|
||||
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_property.view_property_meter_water_tree')}),
|
||||
(0, 0, {'view_mode': 'form', 'view_id': ref('yuthon_property.property_meter_water_form')})]"/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
from . import controllers
|
||||
@@ -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",
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
@@ -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')
|
||||
@@ -0,0 +1 @@
|
||||
from . import hr_employee
|
||||
@@ -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] # 返回字典
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<t t-name="yuthon_web_notice.Announcement">
|
||||
<h1>Hello OWL Web</h1>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -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) });
|
||||
@@ -0,0 +1,14 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<template id="yuthon_web_notice.Announcement" name="Yuthon Web Notice">
|
||||
<html>
|
||||
<head>
|
||||
<link type="image/x-icon" rel="shortcut icon" href="/web/static/img/favicon.ico"/>
|
||||
<t t-call-assets="yuthon_web_notice.assets_announcement"/>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user