fix:解决冲突
This commit is contained in:
+1
-1
@@ -144,4 +144,4 @@ dmypy.json
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Collapsible Notebook',
|
||||
'version': '17.0.1.0.0',
|
||||
'category': 'Productivity',
|
||||
'summary': '支持折叠和展开功能的Notebook组件',
|
||||
'description': """
|
||||
Collapsible Notebook
|
||||
===================
|
||||
|
||||
这个模块提供了一个支持折叠和展开功能的Notebook组件。
|
||||
|
||||
主要功能:
|
||||
* 可折叠的notebook页面
|
||||
* 展开/折叠状态记忆
|
||||
* 美观的UI设计
|
||||
* 响应式布局
|
||||
|
||||
使用方法:
|
||||
在XML视图中使用 <notebook class="o_collapsible_notebook"> 来启用折叠功能。
|
||||
""",
|
||||
'author': 'TX Team',
|
||||
'website': 'https://www.tx.com',
|
||||
'depends': ['base', 'web', 'product'],
|
||||
'assets': {
|
||||
'web.assets_backend': [
|
||||
'collapsible_notebook/static/src/css/collapsible_notebook.css',
|
||||
'collapsible_notebook/static/src/js/collapsible_notebook.js',
|
||||
],
|
||||
},
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
/* 可折叠Notebook样式 */
|
||||
|
||||
/* 整个Notebook折叠按钮样式 */
|
||||
.o_notebook_collapse_btn {
|
||||
display: inline-block;
|
||||
/* margin-right: 10px; */
|
||||
/* margin-bottom: 5px; */
|
||||
/* padding: 6px; */
|
||||
background-color: transparent;
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
user-select: none;
|
||||
vertical-align: top;
|
||||
line-height: 1.4;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 确保按钮和标签页在同一行 */
|
||||
.o_notebook_collapse_btn + .nav-tabs {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: calc(100% - 40px); /* 为按钮留出空间 */
|
||||
}
|
||||
|
||||
/* 为包含按钮的容器设置样式 */
|
||||
.o_notebook .o_notebook_collapse_btn {
|
||||
float: left;
|
||||
margin-top: 5px;
|
||||
width: 30px; /* 固定按钮宽度 */
|
||||
}
|
||||
|
||||
.o_notebook .nav-tabs {
|
||||
overflow: visible;
|
||||
white-space: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
/* 确保导航标签不换行 */
|
||||
.o_notebook .nav-tabs .nav-item {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.o_notebook .nav-tabs .nav-link {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn:active {
|
||||
background-color: #e9e9e9;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn.o_collapsed {
|
||||
background-color: transparent;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn.o_collapsed:hover {
|
||||
background-color: #f5f5f5;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* 折叠状态的整个Notebook */
|
||||
.o_collapsible_notebook.o_notebook_collapsed .o_notebook .tab-content {
|
||||
display: none;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.o_collapsible_notebook.o_notebook_collapsed .o_notebook .nav-tabs {
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* 展开状态的整个Notebook */
|
||||
.o_collapsible_notebook:not(.o_notebook_collapsed) .o_notebook .tab-content {
|
||||
display: block;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.o_collapsible_notebook:not(.o_notebook_collapsed) .o_notebook .nav-tabs {
|
||||
opacity: 1;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
/* 页面内容区域的过渡效果 */
|
||||
.o_notebook .tab-pane {
|
||||
transition: opacity 0.3s ease, height 0.3s ease;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.o_notebook_collapse_btn {
|
||||
font-size: 14px;
|
||||
padding: 4px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn + .nav-tabs {
|
||||
width: calc(100% - 35px);
|
||||
}
|
||||
|
||||
.o_notebook .nav-tabs .nav-link {
|
||||
font-size: 12px;
|
||||
padding: 8px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色主题支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.o_notebook_collapse_btn {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn:hover {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn.o_collapsed {
|
||||
background-color: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.o_notebook_collapse_btn.o_collapsed:hover {
|
||||
background-color: #333;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 展开动画 */
|
||||
.o_notebook .tab-pane.expanding {
|
||||
animation: slideDown 0.3s ease;
|
||||
}
|
||||
|
||||
/* 折叠动画 */
|
||||
.o_notebook .tab-pane.collapsing {
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
/* 工具提示样式 */
|
||||
.o_collapse_btn[title]:hover::after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #333;
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
z-index: 1000;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.o_collapse_btn[title]:hover::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 5px solid transparent;
|
||||
border-top-color: #333;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
/* 高对比度模式支持 */
|
||||
@media (prefers-contrast: high) {
|
||||
.o_notebook_collapse_btn {
|
||||
border: 2px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
/* 减少动画模式支持 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.o_notebook .tab-pane,
|
||||
.o_notebook_collapse_btn,
|
||||
.o_collapsible_notebook {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.o_notebook .tab-pane.expanding,
|
||||
.o_notebook .tab-pane.collapsing {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 打印样式 */
|
||||
@media print {
|
||||
.o_notebook_collapse_btn {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.o_collapsible_notebook.o_notebook_collapsed .o_notebook .tab-content {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.o_collapsible_notebook.o_notebook_collapsed .o_notebook .nav-tabs {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
// 可折叠Notebook功能
|
||||
console.log('Collapsible Notebook JS loaded');
|
||||
|
||||
/**
|
||||
* 页面加载完成后自动初始化所有可折叠notebook
|
||||
*/
|
||||
function initCollapsibleNotebooks() {
|
||||
console.log('Initializing collapsible notebooks...');
|
||||
|
||||
// 初始化现有的notebook
|
||||
const existingNotebooks = document.querySelectorAll('.o_collapsible_notebook');
|
||||
console.log('Found notebooks:', existingNotebooks.length);
|
||||
|
||||
if (existingNotebooks.length === 0) {
|
||||
console.log('No notebooks found, retrying in 500ms...');
|
||||
setTimeout(initCollapsibleNotebooks, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
existingNotebooks.forEach((notebook, index) => {
|
||||
console.log(`Processing notebook ${index}:`, notebook);
|
||||
if (!notebook.hasAttribute('data-collapsible-initialized')) {
|
||||
initializeNotebook(notebook);
|
||||
notebook.setAttribute('data-collapsible-initialized', 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('DOM Content Loaded');
|
||||
initCollapsibleNotebooks();
|
||||
});
|
||||
|
||||
// 如果DOM已经加载完成,立即初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initCollapsibleNotebooks);
|
||||
} else {
|
||||
initCollapsibleNotebooks();
|
||||
}
|
||||
|
||||
// 使用MutationObserver来监听DOM变化,确保在动态加载的内容中也能工作
|
||||
function setupMutationObserver() {
|
||||
if (!document.body) {
|
||||
console.log('Document body not ready, retrying...');
|
||||
setTimeout(setupMutationObserver, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'childList') {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
if (node.nodeType === Node.ELEMENT_NODE) {
|
||||
const notebooks = node.querySelectorAll ?
|
||||
node.querySelectorAll('.o_collapsible_notebook') :
|
||||
(node.classList && node.classList.contains('o_collapsible_notebook') ? [node] : []);
|
||||
|
||||
notebooks.forEach(notebook => {
|
||||
if (!notebook.hasAttribute('data-collapsible-initialized')) {
|
||||
console.log('New notebook detected:', notebook);
|
||||
initializeNotebook(notebook);
|
||||
notebook.setAttribute('data-collapsible-initialized', 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 开始观察
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
console.log('MutationObserver setup complete');
|
||||
}
|
||||
|
||||
// 设置MutationObserver
|
||||
setupMutationObserver();
|
||||
|
||||
/**
|
||||
* 初始化单个notebook
|
||||
*/
|
||||
function initializeNotebook(notebook) {
|
||||
console.log('Initializing notebook:', notebook);
|
||||
// 为整个notebook添加折叠/展开按钮
|
||||
addNotebookCollapseButton(notebook);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为整个notebook添加折叠/展开按钮
|
||||
*/
|
||||
function addNotebookCollapseButton(notebook) {
|
||||
console.log('Adding collapse button to notebook:', notebook);
|
||||
console.log('Notebook HTML structure:', notebook.outerHTML);
|
||||
|
||||
// 检查是否已经添加了按钮
|
||||
if (notebook.querySelector('.o_notebook_collapse_btn')) {
|
||||
console.log('Collapse button already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建notebook折叠按钮
|
||||
const notebookCollapseBtn = document.createElement('div');
|
||||
notebookCollapseBtn.className = 'o_notebook_collapse_btn';
|
||||
notebookCollapseBtn.innerHTML = '<i class="fa fa-chevron-up"></i>';
|
||||
notebookCollapseBtn.title = '点击折叠/展开整个Notebook';
|
||||
|
||||
console.log('Created collapse button:', notebookCollapseBtn);
|
||||
|
||||
// 添加点击事件
|
||||
notebookCollapseBtn.addEventListener('click', (e) => {
|
||||
console.log('Collapse button clicked');
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleNotebook(notebook, notebookCollapseBtn);
|
||||
});
|
||||
|
||||
// 将按钮添加到notebook的nav-tabs前面(与页面标签并排)
|
||||
try {
|
||||
const navTabs = notebook.querySelector('.nav-tabs');
|
||||
if (navTabs && navTabs.parentNode) {
|
||||
navTabs.parentNode.insertBefore(notebookCollapseBtn, navTabs);
|
||||
console.log('Button inserted successfully before nav-tabs');
|
||||
} else if (notebook.parentNode) {
|
||||
notebook.parentNode.insertBefore(notebookCollapseBtn, notebook);
|
||||
console.log('Button inserted successfully before notebook (fallback)');
|
||||
} else {
|
||||
notebook.insertBefore(notebookCollapseBtn, notebook.firstChild);
|
||||
console.log('Button inserted successfully inside notebook (fallback)');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error inserting button:', error);
|
||||
}
|
||||
|
||||
// 恢复折叠状态
|
||||
restoreNotebookState(notebook, notebookCollapseBtn);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从localStorage恢复notebook折叠状态
|
||||
*/
|
||||
function restoreNotebookState(notebook, button) {
|
||||
try {
|
||||
const notebookId = notebook.getAttribute('id') || 'default_notebook';
|
||||
const savedState = localStorage.getItem(`odoo_notebook_collapsed_${notebookId}`);
|
||||
|
||||
if (savedState === 'true') {
|
||||
console.log('Restoring collapsed state for notebook:', notebookId);
|
||||
notebook.classList.add('o_notebook_collapsed');
|
||||
button.innerHTML = '<i class="fa fa-chevron-down"></i>';
|
||||
button.classList.add('o_collapsed');
|
||||
|
||||
// 直接设置内容隐藏
|
||||
const tabContent = notebook.querySelector('.tab-content');
|
||||
if (tabContent) {
|
||||
tabContent.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
console.log('Notebook starts in expanded state:', notebookId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error restoring notebook state:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换整个notebook的折叠/展开状态
|
||||
*/
|
||||
function toggleNotebook(notebook, button) {
|
||||
console.log('Toggling notebook:', notebook);
|
||||
console.log('Notebook classes before:', notebook.className);
|
||||
|
||||
const isCollapsed = notebook.classList.contains('o_notebook_collapsed');
|
||||
console.log('Current state - isCollapsed:', isCollapsed);
|
||||
|
||||
// 查找tab-content元素
|
||||
const tabContent = notebook.querySelector('.tab-content');
|
||||
console.log('Found tab-content:', tabContent);
|
||||
|
||||
if (isCollapsed) {
|
||||
// 展开notebook
|
||||
console.log('Expanding notebook');
|
||||
notebook.classList.remove('o_notebook_collapsed');
|
||||
button.innerHTML = '<i class="fa fa-chevron-up"></i>';
|
||||
button.classList.remove('o_collapsed');
|
||||
|
||||
if (tabContent) {
|
||||
tabContent.style.display = 'block';
|
||||
console.log('Set tab-content display to block');
|
||||
}
|
||||
} else {
|
||||
// 折叠notebook
|
||||
console.log('Collapsing notebook');
|
||||
notebook.classList.add('o_notebook_collapsed');
|
||||
button.innerHTML = '<i class="fa fa-chevron-down"></i>';
|
||||
button.classList.add('o_collapsed');
|
||||
|
||||
if (tabContent) {
|
||||
tabContent.style.display = 'none';
|
||||
console.log('Set tab-content display to none');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Notebook classes after:', notebook.className);
|
||||
|
||||
// 保存状态
|
||||
const notebookId = notebook.getAttribute('id') || 'default_notebook';
|
||||
localStorage.setItem(`odoo_notebook_collapsed_${notebookId}`, isCollapsed ? 'false' : 'true');
|
||||
console.log('State saved to localStorage');
|
||||
}
|
||||
|
||||
@@ -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.
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,
|
||||
|
||||
@@ -672,7 +672,7 @@
|
||||
<field name="water_price"/>
|
||||
<field name="ele_price"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<notebook class="o_collapsible_notebook">
|
||||
<page string="租金明细">
|
||||
<field name="breakdown_ids" string=" ">
|
||||
<tree editable="bottom" delete="false">
|
||||
@@ -764,7 +764,7 @@
|
||||
readonly="1"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<notebook class="o_collapsible_notebook">
|
||||
<page string="附件">
|
||||
<group col="2">
|
||||
<group>
|
||||
|
||||
@@ -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