diff --git a/.gitignore b/.gitignore index 3f2a6d629..d73ada30a 100644 --- a/.gitignore +++ b/.gitignore @@ -144,4 +144,4 @@ dmypy.json # Cython debug symbols cython_debug/ -.DS_Store \ No newline at end of file +.DS_Store diff --git a/.gitignore.swp b/.gitignore.swp new file mode 100644 index 000000000..c0ac53cd5 Binary files /dev/null and b/.gitignore.swp differ diff --git a/collapsible_notebook/__init__.py b/collapsible_notebook/__init__.py new file mode 100644 index 000000000..633f86615 --- /dev/null +++ b/collapsible_notebook/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/collapsible_notebook/__manifest__.py b/collapsible_notebook/__manifest__.py new file mode 100644 index 000000000..4d3cf817e --- /dev/null +++ b/collapsible_notebook/__manifest__.py @@ -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视图中使用 来启用折叠功能。 + """, + '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', +} diff --git a/collapsible_notebook/static/src/css/collapsible_notebook.css b/collapsible_notebook/static/src/css/collapsible_notebook.css new file mode 100644 index 000000000..162ea0b09 --- /dev/null +++ b/collapsible_notebook/static/src/css/collapsible_notebook.css @@ -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; + } +} diff --git a/collapsible_notebook/static/src/js/collapsible_notebook.js b/collapsible_notebook/static/src/js/collapsible_notebook.js new file mode 100644 index 000000000..7e075c692 --- /dev/null +++ b/collapsible_notebook/static/src/js/collapsible_notebook.js @@ -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 = ''; + 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 = ''; + 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 = ''; + 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 = ''; + 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'); +} + 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 deleted file mode 100644 index 10cbe5fd6..000000000 Binary files a/yuthon_hr_employee/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/yuthon_hr_employee/__pycache__/__init__.cpython-311.pyc b/yuthon_hr_employee/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index dbaeb6fdd..000000000 Binary files a/yuthon_hr_employee/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index e8c73eef6..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/yuthon_hr_employee/models/__pycache__/__init__.cpython-311.pyc b/yuthon_hr_employee/models/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index 4adb49824..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/__init__.cpython-311.pyc and /dev/null 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 deleted file mode 100644 index 61e5aa154..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/inherit_res_user.cpython-310.pyc and /dev/null 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 deleted file mode 100644 index ace4a1411..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_hr_department.cpython-310.pyc and /dev/null 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 deleted file mode 100644 index 71dcb9c6e..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_hr_employee.cpython-310.pyc and /dev/null 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 deleted file mode 100644 index 58a3cba3a..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-310.pyc and /dev/null differ diff --git a/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-311.pyc b/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-311.pyc deleted file mode 100644 index 17bb24431..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_job_lv.cpython-311.pyc and /dev/null differ diff --git a/yuthon_hr_employee/models/__pycache__/yuthon_user_role.cpython-310.pyc b/yuthon_hr_employee/models/__pycache__/yuthon_user_role.cpython-310.pyc deleted file mode 100644 index cce32542e..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_user_role.cpython-310.pyc and /dev/null differ diff --git a/yuthon_hr_employee/models/__pycache__/yuthon_user_role.cpython-311.pyc b/yuthon_hr_employee/models/__pycache__/yuthon_user_role.cpython-311.pyc deleted file mode 100644 index 2374f815c..000000000 Binary files a/yuthon_hr_employee/models/__pycache__/yuthon_user_role.cpython-311.pyc and /dev/null differ diff --git a/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc b/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index eb8b92569..000000000 Binary files a/yuthon_hr_employee/wizard/__pycache__/__init__.cpython-310.pyc and /dev/null 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 deleted file mode 100644 index 657d8ae7d..000000000 Binary files a/yuthon_hr_employee/wizard/__pycache__/hr_employee_wizard.cpython-310.pyc and /dev/null differ diff --git a/yuthon_notice/__pycache__/__init__.cpython-310.pyc b/yuthon_notice/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 627b1d1d4..000000000 Binary files a/yuthon_notice/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc b/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 8ff895347..000000000 Binary files a/yuthon_notice/models/__pycache__/__init__.cpython-310.pyc and /dev/null differ diff --git a/yuthon_notice/models/__pycache__/notice_category.cpython-310.pyc b/yuthon_notice/models/__pycache__/notice_category.cpython-310.pyc deleted file mode 100644 index 396c52214..000000000 Binary files a/yuthon_notice/models/__pycache__/notice_category.cpython-310.pyc and /dev/null differ diff --git a/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc b/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc deleted file mode 100644 index c8e59c623..000000000 Binary files a/yuthon_notice/models/__pycache__/yuthon_notice.cpython-310.pyc and /dev/null 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_property_approval/views/property_lease_contract.xml b/yuthon_property_approval/views/property_lease_contract.xml index 6b317a7e3..28399847c 100644 --- a/yuthon_property_approval/views/property_lease_contract.xml +++ b/yuthon_property_approval/views/property_lease_contract.xml @@ -672,7 +672,7 @@ - + @@ -764,7 +764,7 @@ readonly="1"/> - + 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