!7155 添加功能

Merge pull request !7155 from 李鹏宇/dev_zhou
This commit is contained in:
李鹏宇
2025-12-19 07:25:06 +00:00
committed by Gitee
62 changed files with 787 additions and 128 deletions
@@ -6,7 +6,7 @@
- 快捷筛选(quick_filter)
- 自选时间筛选(select_date_filter)(待移植)
- 字段搜索(field)(待移植)
- 字段搜索(field)(已移植)
通过这些筛选标签,用户可以更灵活地对列表视图中的数据进行筛选。
@@ -20,9 +20,9 @@
#### 支持的属性:
| 属性名 | 类型 | 说明 | 是否可选 |
| ------- | ------ | ---------- | ---- |
| `class` | String | 自定义 CSS 类名 | ✅ |
| 属性名 | 类型 | 说明 | 是否可选 |
| ------- | ------ |-------------------------------------------------------------------| ---- |
| `class` | String | 自定义 CSS 类名,<br/>可使用class="header-search-inline"使所有筛选在同一行并根据宽度自动换行 | ✅ |
#### 子标签支持:
@@ -56,7 +56,7 @@
| ----------- | ------- | ----------- | ------------ |
| `class` | String | 自定义 CSS 类名 | ✅ |
| `string` | String | 显示的标头名称 | ✅ |
| `invisible` | Boolean | 是否默认隐藏 | ✅ |
| `invisible` | Boolean | 是否默认隐藏(废弃) | ✅ |
| `options` | JSON | 扩展参数,支持以下参数: | ✅ |
| | | `radio`: 是否单选 | |
| | | `no_label`: 不显示标题 | |
@@ -77,16 +77,16 @@
---
### 3. 自选时间筛选 (select_date_filter)
### 3. 自选时间筛选 (select_date_filter)(废弃)
**标签名:** `select_date_filter`
#### 支持的属性:
| 属性名 | 类型 | 说明 | 是否可选 |
| ------------------- | ------- | -------------- | ---- |
| ------------------- | ------- |----------------| ---- |
| `class` | String | 自定义 CSS 类名 | ✅ |
| `invisible` | Boolean | 是否默认隐藏 | ✅ |
| `invisible` | Boolean | 是否默认隐藏(废弃) | ✅ |
| `allow_date_fields` | String | 允许筛选的时间字段,逗号分隔 | ✅ |
#### 示例:
@@ -109,6 +109,9 @@
| `string` | String | 显示的标头名称 | ✅ |
| `class` | String | 自定义 CSS | ✅ |
| `invisible` | Boolean | 是否默认隐藏 | ✅ |
| `options` | JSON | 扩展参数,支持以下参数: | ✅ |
| | | `radio`: 是否单选 | |
| | | `no_label`: 不显示标题 | |
#### 示例:
@@ -0,0 +1,33 @@
/** @odoo-module **/
//
// import {patch} from "@web/core/utils/patch";
// import * as backend_utils from "@web/legacy/backend_utils";
//
// const originalSearchModelStateToLegacy = backend_utils.searchModelStateToLegacy;
// const originalGetGlobalState = backend_utils.getGlobalState;
//
// patch(backend_utils, "header_search_panel.backend_utils", {
// searchModelStateToLegacy(state) {
// // 原始state
// const originalState = JSON.parse(state);
// // 获取原始state
// let legacyState = JSON.parse(originalSearchModelStateToLegacy(state));
// if (originalState.headerSearchInfo) {
// legacyState.HeaderSearchInfo = originalState.headerSearchInfo;
// }
// return JSON.stringify(legacyState)
// },
//
// getGlobalState(legacyControllerState) {
// // 原始search model
// const originalSearchModel = JSON.parse(legacyControllerState.searchModel)
// let globalState = originalGetGlobalState(legacyControllerState);
// let searchModel = JSON.parse(globalState?.searchModel || "{}");
// if (originalSearchModel.HeaderSearchInfo) {
// searchModel.headerSearchInfo = originalSearchModel.HeaderSearchInfo;
// globalState.searchModel = JSON.stringify(searchModel)
// }
// console.log('headerSearchInfo: getGlobalState')
// return globalState
// },
// })
@@ -0,0 +1,54 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { Domain } from "@web/core/domain";
import { useService } from "@web/core/utils/hooks";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { standardFieldFilterProps } from "../standard_field_filter_props.js"
export class CharFilter extends Component {
setup() {
super.setup();
this.rpc = useService("rpc");
this.activeValues = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.activeValues || undefined;
}
/**
* 设置筛选字段的值
*/
setActiveValues(ev) {
this.activeValues = ev.target.value;
}
/**
* 获取筛选字段的domain
*/
get domain() {
return this.activeValues ? [[this.props.name, "ilike", this.activeValues]]: [];
}
/**
* 点击清空筛选时触发
*/
resetFilter() {
// 清空筛选字段的值
this.activeValues = undefined;
this.props.fieldOnchange({fieldDomainList: this.domain, activeValues: this.activeValues});
}
getDomain() {
return new Domain(this.props.domain).toList({});
}
async onInputChange(ev) {
this.setActiveValues(ev);
this.props.fieldOnchange({fieldDomainList: this.domain, activeValues: this.activeValues});
}
}
CharFilter.template = "header_search_panel.CharFilter";
CharFilter.components = {};
CharFilter.props = {
...standardFieldFilterProps,
};
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="header_search_panel.CharFilter" owl="1">
<div>
<input class="o_input"
t-att-id="props.id"
type="text"
t-ref="input"
t-att-value="activeValues"
t-on-input="onInputChange"/>
</div>
</t>
</templates>
@@ -1,8 +1,9 @@
/** @odoo-module **/
import {Component} from "@odoo/owl";
import {Domain} from "@web/core/domain";
import {standardFieldFilterProps} from "../standard_field_filter_props.js"
import { Component } from "@odoo/owl";
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { deserializeDate } from "@web/core/l10n/dates";
import { standardFieldFilterProps } from "../standard_field_filter_props.js"
export class DateFilter extends Component {
setup() {
@@ -13,13 +14,21 @@ export class DateFilter extends Component {
this.endDate = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.endDate;
}
get dateFrom() {
return this.startDate ? deserializeDate(this.startDate): undefined;
}
get dateTo() {
return this.endDate ? deserializeDate(this.endDate): undefined;
}
// 设置筛选字段的值
setActiveValues(ev) {
const value = ev.target.value;
if (ev.target.id === this.props.id + "_start_date") {
setActiveValues(dateValue, type) {
const value = dateValue ? (this.props.type === "datetime" ? dateValue.toFormat("yyyy-MM-dd HH:mm:ss") : dateValue.toFormat("yyyy-MM-dd")) : undefined;
if (type === "startDate") {
this.startDate = value
}
if (ev.target.id === this.props.id + "_end_date") {
if (type === "endDate") {
this.endDate = value
}
}
@@ -41,9 +50,14 @@ export class DateFilter extends Component {
return domain
}
// 点击日期选择器时触发
dateOnchange(ev) {
this.setActiveValues(ev);
startDateOnchange (startDate) {
this.setActiveValues(startDate, "startDate");
this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate});
}
endDateOnchange (endDate) {
this.setActiveValues(endDate, "endDate");
this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate});
}
@@ -67,6 +81,9 @@ export class DateFilter extends Component {
}
DateFilter.template = "header_search_panel.DateFilter";
DateFilter.components = {
DateTimeInput,
};
DateFilter.props = {
...standardFieldFilterProps,
};
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<!-- selection字段筛选框 -->
<t t-name="header_search_panel.DateFilter" owl="1">
<!-- 选择字段选择项 -->
<div class="date-filter">
<DateTimeInput value="dateFrom"
type="props.type"
onChange.bind="startDateOnchange"/>
<i class="oi oi-arrow-right"/>
<DateTimeInput value="dateTo"
type="props.type"
onChange.bind="endDateOnchange"/>
<!-- <input -->
<!-- t-att-type="props.type" -->
<!-- t-attf-id="{{ props.id + '_start_date' }}" -->
<!-- name="start_date" -->
<!-- t-att-value="this.startDate" -->
<!-- t-on-change="dateOnchange"/> -->
<!-- <input -->
<!-- t-att-type="props.type" -->
<!-- t-attf-id="{{ props.id + '_end_date' }}" -->
<!-- name="end_date" -->
<!-- t-att-value="this.endDate" -->
<!-- t-on-change="dateOnchange"/> -->
</div>
<!-- <div class="fast-date-filter"> -->
<!-- <a href="#" id="one-month" t-on-click="() => this.setDateRange(1)">近1个月</a> -->
<!-- <a href="#" id="three-months" t-on-click="() => this.setDateRange(3)">近3个月</a> -->
<!-- <a href="#" id="half-year" t-on-click="() => this.setDateRange(6)">近半年</a> -->
<!-- <a href="#" id="one-year" t-on-click="() => this.setDateRange(12)">近1年</a> -->
<!-- <a href="#" id="two-years" t-on-click="() => this.setDateRange(24)">近2年</a> -->
<!-- </div> -->
</t>
</templates>
@@ -8,6 +8,7 @@ import { BooleanFilter } from "./boolean_filter/boolean_filter";
import { DateFilter } from "./date_filter/date_filter";
import { Many2oneFilter } from "./many2one_filter/many2one_filter";
import { X2ManyFilterFilter } from "./x2many_filter/x2many_filter";
import { CharFilter } from "./char_filter/char_filter";
const fieldRegistry = registry.category("fields");
@@ -41,6 +42,9 @@ export class FieldsFilter extends Component {
case "one2many":
this.addX2ManyFieldDependencies(fieldInfo)
break;
case "char":
this.addCharFieldDependencies(fieldInfo)
break;
}
return fieldInfo;
}
@@ -65,6 +69,9 @@ export class FieldsFilter extends Component {
case "many2many":
case "one2many":
return X2ManyFilterFilter;
case "char":
return CharFilter;
}
}
@@ -128,6 +135,7 @@ export class FieldsFilter extends Component {
domain: field.fieldDomain || "[]",
fieldString: field.fieldString,
searchLimit: field.fieldOptions?.search_limit || 10,
placeholder: field.placeholder,
}
this._addFieldBaseProps(field.props, field);
}
@@ -144,6 +152,13 @@ export class FieldsFilter extends Component {
this._addFieldBaseProps(field.props, field);
}
addCharFieldDependencies(field) {
field.Component = this._getComponent(field);
// 组件props
field.props = {};
this._addFieldBaseProps(field.props, field);
}
// 筛选字段的onChange事件,将筛选字段的id和domain传给searchModel
fieldFilterOnchange(fieldsFilterId, fieldsFilterInfo) {
if (fieldsFilterInfo?.fieldDomainList){
@@ -104,6 +104,7 @@ export class Many2oneFilter extends Component {
activeActions: {create: false, createEdit: false, write: false},
fieldString: this.props.fieldString,
searchLimit: this.props.searchLimit,
placeholder: this.props.placeholder,
};
}
}
@@ -13,4 +13,6 @@ export const standardFieldFilterProps = {
type: {type: String, required: true},
// 字段变换监听函数
fieldOnchange: { type: Function, required: true },
// 提示
placeholder: {type: String, optional: true},
};
@@ -2,11 +2,11 @@
<templates xml:space="preserve">
<!-- 快速筛选组件 -->
<t t-name="header_search_panel.QuickFilter" owl="1">
<!-- <div t-if="props.showLabel" class="header-filter-label">
<div t-if="props.showLabel" class="header-filter-label">
<span>
<t t-esc="props.string" />
</span>
</div> -->
</div>
<!-- quick filter 内的筛选循环 -->
<div class="header-filter-value">
<div class="selection-options">
@@ -74,10 +74,12 @@
.fast-date-filter {
display: flex;
flex-wrap: wrap;
align-items: center;
}
input[type="date"],
.o_datetime_input,
.fast-date-filter a {
width: auto;
margin: 0.2rem;
padding: 0.1rem 1rem;
border: 0.1rem solid $o-community-color; /* 将边框颜色改为黑色 */
@@ -149,6 +151,14 @@
}
}
}
.header-search-inline {
display: flex;
flex-wrap: wrap;
align-items: start;
gap: 5px;
}
.header_search_toggle {
width: 6rem;
height: 2rem;
@@ -161,4 +171,4 @@
line-height: 2rem;
margin-left: 0.5rem;
}
}
}
@@ -165,6 +165,8 @@ patch(SearchArchParser.prototype, {
const fieldString = node.getAttribute("string");
// 自定义css类
const cssClass = node.getAttribute("class");
// placeholder
const placeholder = node.getAttribute("placeholder");
// 获取字段的options属性,该属性代表字段选项
const fieldOptions = evaluateExpr(node.getAttribute("options") || "{}");
// 获取widget
@@ -173,7 +175,7 @@ patch(SearchArchParser.prototype, {
if (fieldName) {
const field = this.fields[fieldName];
// 如果字段类型为selection或者bool,将字段名和选项添加到headerSearchInfo的fields中
if (!["selection", "boolean", "date", "datetime", "many2one", "one2many", "many2many"].includes(field.type)) {
if (!["selection", "boolean", "date", "datetime", "many2one", "one2many", "many2many", "char"].includes(field.type)) {
return false;
}
const fieldInfo = {
@@ -186,6 +188,7 @@ patch(SearchArchParser.prototype, {
fieldOptions: fieldOptions,
modifiers: JSON.parse(node.getAttribute("modifiers") || "{}"),
class: cssClass,
placeholder: placeholder || undefined,
}
if (["many2one", "many2many"].includes(field.type)) {
fieldInfo.fieldDomain = node.getAttribute("domain");
@@ -237,17 +240,16 @@ patch(SearchArchParser.prototype, {
for (const node of headerSearchNode.children) {
switch (node.tagName) {
case "field":
// // 字段筛选
// this.visitHeaderField(node);
// 字段筛选
this.visitHeaderField(node);
break;
case "quick_filter":
// 快捷筛选
this.visitHeaderQuickFilter(node);
break;
case "select_date_filter":
// // 自选日期筛选
// this.visitHeaderSelectDateFilter(node);
break;
// 自选日期筛选
this.visitHeaderSelectDateFilter(node);
}
}
return false;
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
{
'name': '模型测试',
'summary': '',
'description': '',
'author': 'XSK',
'website': '',
'category': 'Uncategorized',
'version': '0.1',
'license': 'LGPL-3',
'depends': ['base', 'web', 'mail'],
'data': [
'security/ir.model.access.csv',
'views/views.xml',
],
'assets': {
'web.assets_backend': [
'model_test/static/src/**/*',
],
},
}
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# from odoo import http
# class FilePreviewTest(http.Controller):
# @http.route('/file_preview_test/file_preview_test', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/file_preview_test/file_preview_test/objects', auth='public')
# def list(self, **kw):
# return http.request.render('file_preview_test.listing', {
# 'root': '/file_preview_test/file_preview_test',
# 'objects': http.request.env['file_preview_test.file_preview_test'].search([]),
# })
# @http.route('/file_preview_test/file_preview_test/objects/<model("file_preview_test.file_preview_test"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('file_preview_test.object', {
# 'object': obj
# })
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
@@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
from datetime import datetime, date, timedelta
import string
import random
from odoo import models, fields, api
class ModelTestManyOne(models.Model):
_name = 'model.test.many.one'
_description = '测试模块'
_inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char(string='字符串字段', tracking=True)
test_id = fields.Many2one(string='多对一字段', comodel_name='model.test', required=True, ondelete='cascade')
file_id = fields.Many2one(string='多对一文件', comodel_name='ir.attachment', tracking=True)
file_ids = fields.Many2many(string='多对多文件', comodel_name='ir.attachment', relation='file_preview_test_many_one_rel', column1='test_id', column2='test_id_2')
class ModelTest(models.Model):
_name = 'model.test'
_description = '测试模块'
_inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char(string='字符串字段', tracking=True)
int_field = fields.Integer(string='整数', tracking=True)
float_field = fields.Float(string='浮点数', tracking=True)
date_field = fields.Date(string='日期字段', tracking=True)
datetime_field = fields.Datetime(string='日期时间字段', tracking=True)
state = fields.Selection(string='状态', selection=[('draft', '草稿'), ('done', '完成')], default='draft')
file_id = fields.Many2one(string='多对一文件', comodel_name='ir.attachment')
file_ids = fields.Many2many(string='多对多文件', comodel_name='ir.attachment', relation='file_preview_test_rel', column1='test_id', column2='test_id_2')
text = fields.Text(string='长文本测试')
one_to_many_field = fields.One2many(string='one2many字段', comodel_name='model.test.many.one', inverse_name='test_id', tracking=True)
bool_test = fields.Boolean(string='布尔字段')
selection_test = fields.Selection(string='选择字段', selection=[('1', '选项一'), ('2', '选项二')])
def action_generate_sample_text(self):
"""生成长文本样例"""
sample_text = """
# 这是Markdown格式的样例文本
## 项目概述
这是一个测试项目,用于展示Odoo中各种字段类型的功能和显示效果。
### 功能特点
1. 支持多种数据类型
2. 自动生成测试数据
3. 便于开发和测试
### 技术规格
- 数据库:PostgreSQL
- 框架:Odoo 14.0+
- 语言:Python/XML/JavaScript
---
**注意**:此内容为自动生成,仅用于测试目的。
"""
self.write({'text': sample_text})
# 辅助方法
def _generate_random_string(self, length):
"""生成随机字符串"""
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for _ in range(length))
def _generate_random_date(self):
"""生成随机日期(最近365天内)"""
days_ago = random.randint(0, 365)
return date.today() - timedelta(days=days_ago)
def _generate_random_datetime(self):
"""生成随机日期时间(最近30天内)"""
hours_ago = random.randint(0, 720) # 30天*24小时
return datetime.now() - timedelta(hours=hours_ago)
# 生成随机数据的方法
def action_insert_random_data(self):
"""生成50条随机测试数据"""
# 预先生成一些附件供选择
attachment_ids = self.env['ir.attachment'].search([], limit=10)
for i in range(50):
# 生成随机数据
random_name = f"测试项目_{i + 1}_{self._generate_random_string(6)}"
random_int = random.randint(1, 1000)
random_float = round(random.uniform(1.0, 10000.0), 2)
random_date = self._generate_random_date()
random_datetime = self._generate_random_datetime()
random_state = random.choice(['draft', 'done'])
random_selection = random.choice(['1', '2'])
random_bool = random.choice([True, False])
# 生成随机文本
random_text = f"""
这是第{i + 1}条测试记录的长文本内容。
生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
随机ID:{self._generate_random_string(10)}
描述:这是一条自动生成的测试数据,用于演示各种字段类型的显示效果。
"""
# 创建主记录
test_record = self.create({
'name': random_name,
'int_field': random_int,
'float_field': random_float,
'date_field': random_date,
'datetime_field': random_datetime,
'state': random_state,
'bool_test': random_bool,
'selection_test': random_selection,
'text': random_text,
})
# 随机选择附件
if attachment_ids:
# 随机选择1-3个附件
num_attachments = random.randint(1, min(3, len(attachment_ids)))
random_attachments = random.sample(attachment_ids.ids, num_attachments)
test_record.write({
'file_id': random.choice(attachment_ids).id if attachment_ids else False,
'file_ids': [(6, 0, random_attachments)]
})
# 返回成功消息
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': '数据生成成功',
'message': f'已成功生成50条测试数据!',
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
def action_to_dialog(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': 'model.test',
'view_mode': 'form',
'res_id': self.id,
'views': [[self.env.ref('model_test.view_file_preview_test_form').id, 'form']],
}
def cancel(self):
self.state = 'done'
def test_approval(self):
self.verify_rules(self.cancel)
# print(asd)
# class TimeCalculation(models.Model):
# _name = 'example.time.calculation'
# _description = 'Example for Start Time, End Time, Duration'
#
# start_time = fields.Datetime(string='Start Time', required=True)
# end_time = fields.Datetime(string='End Time', required=True)
# duration = fields.Float(string='Duration (hours)', compute='_compute_duration', inverse='_inverse_duration', store=True)
#
# @api.depends('start_time', 'end_time')
# def _compute_duration(self):
# """根据开始时间和结束时间计算时间间隔"""
# for record in self:
# if record.start_time and record.end_time:
# delta = record.end_time - record.start_time
# record.duration = delta.total_seconds() / 3600 # 转换为小时
#
# @api.depends('start_time', 'duration')
# def _compute_end_time(self):
# """根据开始时间和时间间隔计算结束时间"""
# for record in self:
# if record.start_time and record.duration is not None:
# record.end_time = record.start_time + timedelta(hours=record.duration)
#
# @api.depends('end_time', 'duration')
# def _compute_start_time(self):
# """根据结束时间和时间间隔计算开始时间"""
# for record in self:
# if record.end_time and record.duration is not None:
# record.start_time = record.end_time - timedelta(hours=record.duration)
#
# def _inverse_duration(self):
# """反向更新结束时间"""
# for record in self:
# if record.start_time and record.duration is not None:
# record.end_time = record.start_time + timedelta(hours=record.duration)
#
# @api.onchange('start_time', 'end_time')
# def _onchange_duration(self):
# """确保在界面交互中,时间关系自动更新"""
# for record in self:
# if record.start_time and record.end_time:
# delta = record.end_time - record.start_time
# record.duration = delta.total_seconds() / 3600
#
# @api.onchange('start_time', 'duration')
# def _onchange_end_time(self):
# """确保在界面交互中,时间关系自动更新"""
# for record in self:
# if record.start_time and record.duration is not None:
# record.end_time = record.start_time + timedelta(hours=record.duration)
#
# @api.onchange('end_time', 'duration')
# def _onchange_start_time(self):
# """确保在界面交互中,时间关系自动更新"""
# for record in self:
# if record.end_time and record.duration is not None:
# record.start_time = record.end_time - timedelta(hours=record.duration)
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_model_test,测试模块,model_model_test,,1,1,1,1
access_model_test_many_one,测试模块多,model_model_test_many_one,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_model_test 测试模块 model_model_test 1 1 1 1
3 access_model_test_many_one 测试模块多 model_model_test_many_one 1 1 1 1
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="ModelTest.buttons" t-inherit="web.ListView.Buttons" owl="1">
<xpath expr="(//div/*)[last()]" position="after">
<button type="button" class="btn btn-primary"
t-on-click="(ev) => this.triggerDynamicColumnsChanged('btn_1', ['name','int_field','float_field'])">
方案1
</button>
<button type="button" class="btn btn-primary"
t-on-click="(ev) => this.triggerDynamicColumnsChanged('btn_2', ['selection_test','text','one_to_many_field'])">
方案2
</button>
</xpath>
</t>
</templates>
@@ -0,0 +1,10 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { listView } from '@web/views/list/list_view';
export const ModelTestListView = {
...listView,
buttonTemplate: 'ModelTest.buttons'
};
registry.category('views').add('model_test_list', ModelTestListView);
@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- 项目总览搜索 -->
<record id="view_model_test_search_view" model="ir.ui.view">
<field name="model">model.test</field>
<field name="arch" type="xml">
<search string="项目总览">
<separator/>
<filter string="真" name="filter_by_true" domain="[('bool_test', '=', True)]"/>
<separator/>
<filter string="假" name="filter_by_false" domain="[('bool_test', '=', False)]"/>
<group expand="0" string="Group By">
<filter string="多对一未见" name="group_by_file_id" domain="[]" context="{'group_by': 'file_id'}" />
<filter string="布尔值" name="group_by_bool_test" domain="[]" context="{'group_by': 'bool_test'}" />
</group>
<searchpanel>
<field name="file_id"/>
</searchpanel>
<!--
<header_search class="header-search-inline"> 所有搜索在一行
-->
<header_search>
<field name="name"/>
<field name="file_id"/>
<field name="selection_test" options="{'radio': True}"/>
<field name="bool_test" options="{'radio': True, 'no_label': True}"/>
<select_date_filter allow_date_fields="create_date,write_date" />
<quick_filter string="快捷筛选一" options="{'radio': True, 'no_label': True}">
<filter string="真" name="filter_by_bool_true" domain="[('bool_test', '=', True)]"/>
<filter string="最近的二十" name="filter_by_bool_true"/>
</quick_filter>
<field name="date_field" options="{'radio': True, 'no_label': True}"/>
<field name="datetime_field"/>
<quick_filter string="快捷筛选二" options="{'radio': True, 'no_label': True}">
<filter string="假" name="filter_by_bool_false" domain="[('bool_test', '=', False)]"/>
</quick_filter>
</header_search>
</search>
</field>
</record>
<!-- tree视图 -->
<record id="view_model_test_tree" model="ir.ui.view">
<field name="name">model.test.tree</field>
<field name="model">model.test</field>
<field name="arch" type="xml">
<tree string="">
<header>
<button name="action_insert_random_data" string="随机插入50条数据" type="object" class="btn-primary ms-1" display="always"/>
</header>
<field name="name"/>
<field name="int_field"/>
<field name="float_field"/>
<field name="selection_test"/>
<field name="text" widget="text_nowrap"/>
<field name="one_to_many_field"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- form视图 -->
<record id="view_model_test_form" model="ir.ui.view">
<field name="name">model.test.form</field>
<field name="model">model.test</field>
<field name="arch" type="xml">
<form string="Job">
<header>
<button string="测试审批" type="object" name="test_approval" class="btn-primary"/>
</header>
<sheet>
<group>
<field name="name"/>
<field name="int_field"/>
<field name="float_field"/>
<field name="date_field"/>
<field name="datetime_field"/>
<field name="state"/>
<field name="text"/>
<field name="file_id"/>
<field name="selection_test"/>
<field name="bool_test" widget="boolean_toggle"/>
<field name="file_ids"/>
</group>
<notebook>
<page string="多对多字段图片预览测试">
<field name="file_ids"/>
</page>
<page string="one2many字段">
<field name="one_to_many_field" mode="tree">
<tree editable="1">
<field name="name"/>
<field name="file_id"/>
<field name="file_ids"/>
</tree>
</field>
</page>
</notebook>
</sheet>
<!-- Attachment preview -->
<div class="o_attachment_preview" groups="hr_recruitment.group_applicant_cv_display"/>
<div class="oe_chatter">
<field name="message_ids"/>
</div>
</form>
</field>
</record>
<!-- 跳转菜单 -->
<record model="ir.actions.act_window" id="file_preview_menu_action_window">
<field name="name">文件测试</field>
<field name="target">main</field>
<field name="res_model">model.test</field>
<field name="view_mode">tree,form</field>
</record>
<!-- 二级菜单 -->
<menuitem
id="model_test_menu"
name="测试模块"
sequence="10"
action="file_preview_menu_action_window"/>
</data>
</odoo>
@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<!-- selection字段筛选框 -->
<t t-name="header_search_panel.DateFilter" owl="1">
<!-- 选择字段选择项 -->
<div class="date-filter">
<input
type="date"
t-attf-id="{{ props.id + '_start_date' }}"
name="start_date"
t-att-value="this.startDate"
t-on-change="dateOnchange"/>
<input
type="date"
t-attf-id="{{ props.id + '_end_date' }}"
name="end_date"
t-att-value="this.endDate"
t-on-change="dateOnchange"/>
</div>
<div class="fast-date-filter">
<a href="#" id="one-month" t-on-click="() => this.setDateRange(1)">近1个月</a>
<a href="#" id="three-months" t-on-click="() => this.setDateRange(3)">近3个月</a>
<a href="#" id="half-year" t-on-click="() => this.setDateRange(6)">近半年</a>
<a href="#" id="one-year" t-on-click="() => this.setDateRange(12)">近1年</a>
<a href="#" id="two-years" t-on-click="() => this.setDateRange(24)">近2年</a>
</div>
</t>
</templates>
@@ -18,7 +18,7 @@
<filter name="is_start" string="草稿" domain="[('approve_state', '=', 'start')]" />
<filter name="is_to_do" string="待办" domain="[('soong_state', '=', 'to_do')]"/>
<group expand="0" string="审批分组">
<filter string="误餐费" name='group_by_workflow_id' context="{'group_by':'meal'}"/>
<filter string="误餐费" name='group_by_meal' context="{'group_by':'meal'}"/>
<filter string="审批类型分组" name='group_by_approvals_group_id' context="{'group_by':'approvals_group_id'}"/>
<filter string="状态" name="group_soong_state" context="{'group_by':'soong_state'}"/>
</group>
@@ -51,7 +51,9 @@
<field name="text" optional="hide"/>
<field name="documents_ids" optional="hide"/>
<group expand="0" string="审批分组">
<filter string="误餐费" name='group_by_workflow_id' context="{'group_by':'meal'}"/>
<filter string="审批状态" name='group_by_approve_state' context="{'group_by':'approve_state'}"/>
<filter string="审批类型" name='group_by_workflow_id' context="{'group_by':'workflow_id'}"/>
<filter string="误餐费" name='group_by_meal' context="{'group_by':'meal'}"/>
<filter string="审批类型分组" name='group_by_approvals_group_id' context="{'group_by':'approvals_group_id'}"/>
<filter string="状态" name="group_soong_state" context="{'group_by':'soong_state'}"/>
</group>
@@ -145,8 +145,8 @@ class SoongWorkflowCommentWizard(models.TransientModel):
for idx, link in enumerate(sorted_all_links):
if link.id == current_link.id and link.sequence == current_link.sequence:
approvers = self._get_approvers_for_link(link, record_id)
if not approvers:
invalid_links |= link
# if not approvers:
# invalid_links |= link
if record_id.log_ids2:
log_records = record_id.log_ids2
+5 -43
View File
@@ -118,32 +118,6 @@ class HrLeave(models.Model):
hour_batch_id = fields.Many2one(string='批量', comodel_name='hr.leave.batch', ondelete='cascade')
leave_documents_ids = fields.Many2many(related='holiday_status_id.leave_documents_id', string="假期详情说明", readonly=True)
details = fields.Text(string="假期流程说明")
request_hour_from_real = fields.Selection([
('0', '0:00'), ('0.5', '0:30'),
('1', '01:00'), ('1.5', '01:30'),
('2', '02:00'), ('2.5', '02:30'),
('3', '03:00'), ('3.5', '03:30'),
('4', '04:00'), ('4.5', '04:30'),
('5', '05:00'), ('5.5', '05:30'),
('6', '06:00'), ('6.5', '06:30'),
('7', '07:00'), ('7.5', '07:30'),
('8', '08:00'), ('8.5', '08:30'),
('9', '09:00'), ('9.5', '09:30'),
('10', '10:00'), ('10.5', '10:30'),
('11', '11:00'), ('11.5', '11:30'),
('12', '12:00'), ('12.5', '12:30'),
('13', '13:00'), ('13.5', '13:30'),
('14', '14:00'), ('14.5', '14:30'),
('15', '15:00'), ('15.5', '15:30'),
('16', '16:00'), ('16.5', '16:30'),
('17', '17:00'), ('17.5', '17:30'),
('18', '18:00'), ('18.5', '18:30'),
('19', '19:00'), ('19.5', '19:30'),
('20', '20:00'), ('20.5', '20:30'),
('21', '21:00'), ('21.5', '21:30'),
('22', '22:00'), ('22.5', '22:30'),
('23', '23:00'), ('23.5', '23:30')],
string='实际开始时间', compute='_compute_request_hour_from_real')
# custom_document_ids = fields.Many2many(
# string='文件',
# comodel_name='ir.attachment',
@@ -209,14 +183,6 @@ class HrLeave(models.Model):
for leave in leave_ids:
leave.state = 'draft'
@api.depends('request_hour_from')
def _compute_request_hour_from_real(self):
for record in self:
if record.request_hour_from == '9':
record.request_hour_from_real = '8'
else:
record.request_hour_from_real = record.request_hour_from
@api.depends('date_from', 'date_to', 'resource_calendar_id', 'holiday_status_id.request_unit')
def _compute_duration(self):
super()._compute_duration()
@@ -229,6 +195,7 @@ class HrLeave(models.Model):
else:
hours = 0.0
holiday.number_of_hours = hours
holiday.leave_hours = hours
if holiday.holiday_status_id.type_of_work == "0":
if holiday.request_hour_from and holiday.request_hour_to:
start_hour = float(holiday.request_hour_from)
@@ -236,13 +203,17 @@ class HrLeave(models.Model):
if start_hour < 12 and end_hour > 12:
hour = end_hour - start_hour - 1
holiday.number_of_hours = hour
holiday.leave_hours = hour
elif start_hour == 9 and end_hour == 12:
holiday.number_of_hours = 4
holiday.leave_hours = 4
else:
hour = end_hour - start_hour
holiday.number_of_hours = hour
holiday.leave_hours = hour
else:
holiday.number_of_hours = 0
holiday.leave_hours = 0
# 按天请假
days = holiday.batch_id._compute_duration_day(holiday.holiday_status_id, holiday.request_date_from , holiday.request_date_to)
@@ -686,7 +657,6 @@ class HolidaysRequestBatch(models.Model):
total_days = 0
first_hour_leave = self.hour_leave_ids[0] if self.hour_leave_ids else None
holiday_status_names = []
# 处理leave_ids:收集假期类型、日期和天数
for leave in self.leave_ids:
# 收集假期类型名称(去重)
if leave.holiday_status_id and leave.holiday_status_id.name:
@@ -700,10 +670,8 @@ class HolidaysRequestBatch(models.Model):
if leave.request_date_to:
all_dates.append(leave.request_date_to)
# 累加天数
total_days += leave.number_of_days_display if hasattr(leave, 'number_of_days_display') else 0
# 处理hour_leave_ids:收集日期
for line in self.hour_leave_ids:
if line.request_date_from:
all_dates.append(line.request_date_from)
@@ -721,17 +689,11 @@ class HolidaysRequestBatch(models.Model):
# 生成名称:员工名 + 假期类型 + 日期范围 + 天数/小时
if self.employee_id:
name_parts = [f"{self.employee_id.name}的"]
# 拼接假期类型
if holiday_status_names:
name_parts.append(','.join(holiday_status_names))
# 拼接日期范围
if self.start_date and self.end_date:
date_part = f"{self.start_date.strftime('%Y-%m-%d')}~{self.end_date.strftime('%Y-%m-%d')}"
name_parts.append(date_part)
# 拼接整请假天数
if self.leave_ids:
name_parts.append(f"共计{int(total_days)}天")
+1 -2
View File
@@ -70,6 +70,7 @@
'default_employee_id': employee_id,
}" string=" " style="width: 150%;">
<tree editable="bottom">
<field name="number_of_days_display" string="天" width="100px"/>
<field name="employee_id" readonly="1" force_save="1" column_invisible="1"/>
<field name="state" column_invisible="True"/>
<field name="employee_company_id" column_invisible="True"/>
@@ -116,7 +117,6 @@
<!-- />-->
<field name="request_date_from" width="100px" string="开始日期"/>
<field name="request_date_to" width="100px" string="结束日期"/>
<field name="number_of_days_display" string="天" width="100px"/>
<field name="leave_documents_ids" widget="preview_many2many" options="{'preview_type': 'button', 'preview_string': ' '}" string=" " width="100px" readonly='true'/>
<field name="request_hour_to" column_invisible="True"/>
</tree>
@@ -230,7 +230,6 @@
<field name="request_unit_hours" invisible="True" force_save="1"/>
<field name="request_date_from" width="100px" string="请假日期"/>
<field name="request_hour_from" width="200px"/>
<field name="request_hour_from_real" invisible="True"/>
<field name="request_hour_to" width="200px"/>
<field name="request_date_to" invisible="True"/>
<field name="number_of_hours_text" string="小时" width="100px" invisible="True"/>
@@ -564,6 +564,16 @@
</tree>
</field>
</record>
<record id="member_view_search" model="ir.ui.view">
<field name="name">hr.employee.search</field>
<field name="model">hr.employee</field>
<field name="arch" type="xml">
<search string="工会会员">
<field name="name"/>
<field name="department_id"/>
</search>
</field>
</record>
<!--会员列表-->
<record id="view_employee_member_tree" model="ir.ui.view">
<field name="name">hr.employee.member</field>
@@ -574,7 +584,7 @@
<field name="work_phone" readonly="1"/>
<field name="department_id" readonly="1" options='{"no_open": True, "no_create": True}'/>
<field name="job_id" readonly="1" options='{"no_open": True, "no_create": True}'/>
<field name="is_company" optional="hide"/>
<field name="is_company" column_invisible="1"/>
<field name="company_id" column_invisible="1"/>
</tree>
</field>
@@ -591,7 +601,6 @@
<xpath expr="//filter[@name='group_manager']" position="after">
<filter name="group_is_membership" string="工会会员" context="{'group_by': 'is_membership'}"/>
</xpath>
<xpath expr="//search" position="inside">
<header_search>
<quick_filter string="状态">
@@ -659,11 +668,11 @@
<field name="name">会员列表</field>
<field name="res_model">hr.employee</field>
<field name="domain">[('is_membership', '=', True), ('is_company', '=', True)]</field>
<field name="context">{'header_search_default_user_state': 1}</field>
<field name="view_mode">tree,kanban,form,search</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('yuthon_hr_employee.view_employee_member_tree')}),
(0, 0, {'view_mode': 'form', 'view_id': ref('hr.view_employee_form')})]"/>
<field name="search_view_id" ref="member_view_search"/>
</record>
<record id="hr.open_view_employee_list_my" model="ir.actions.act_window">
<field name="view_mode">tree,kanban,form,activity,graph,pivot,hierarchy</field>
@@ -23,8 +23,8 @@ class YuthonOfficialIncomingNormal(models.Model):
secret_level = fields.Selection([('normal', '一般'), ('urgent', '秘密'), ('secret', '机密'), ('so_secret', '绝密')], string="密级")
time_date = fields.Datetime(string="日期", default=fields.Datetime.now)
users_id = fields.Many2one('res.users', string="拟办人", default=lambda self: self.env.user.id)
department_id = fields.Many2one(related='users_id.employee_id.department_id', string="拟办部门")
company_id = fields.Many2one(related='users_id.employee_id.company_id', string="公司")
department_id = fields.Many2one(related='users_id.employee_ids.department_id', string="拟办部门")
company_id = fields.Many2one(related='users_id.employee_ids.company_id', string="公司")
department_data = fields.Html(string="拟办意见")
red_suit = fields.Char(string="套红")
official_statu = fields.Selection([('done', '办结'), ('approval', '审核')], string="公文环节")
+1
View File
@@ -94,6 +94,7 @@
'wizard/contract_signature_wizard_viwes.xml',
'wizard/penalty_line_wizard_viwes.xml',
'wizard/build_pop_wizard_viwes.xml',
'wizard/action_refuse_wizard_views.xml',
'wizard/property_bills_wizard_views.xml',
'wizard/property_meeting_wizard_views.xml',
'views/property_menu_all.xml',
@@ -873,7 +873,16 @@ class PropertyAccountsReceivable(models.Model):
valid_management_types = list(model_type_data.values())
for rec in archives_ids:
if rec.is_reconciliation == True:
raise UserError("已对账的记录不能退回!请先银行流水撤销再退回")
return {
"name": "批量退回提醒弹窗",
'type': 'ir.actions.act_window',
'res_model': 'action.refuse.wizard',
"view_mode": "form",
'target': 'new',
'context': {
'default_receivable_ids': archives_ids.ids,
}
}
model_type = model_type_data.get(rec.management_type_id.name)
if model_type not in valid_management_types:
rec.unlink()
@@ -1636,7 +1636,6 @@ class PropertyLeaseContract(models.Model):
electricity_list = []
water_list = []
title_address = []
fee_list = []
for record in rec.lease_ids:
if record.address_id and record.address_id.title_address:
lease_name.append(record.address_id.name)
@@ -1963,7 +1962,6 @@ class PropertyLeaseContract(models.Model):
def _onchange_name(self):
if self.property_name:
self.address_for_delivery = self.property_name
self.use_address = self.property_name
@api.onchange("admin_id")
def _onchange_admin_id(self):
@@ -121,4 +121,5 @@ access_property_bills_line,property_bills_line,model_property_bills_line,base.gr
access_property_payment_notice,property_payment_notice,model_property_payment_notice,base.group_user,1,1,1,1
access_print_payment_wizard,print_payment_wizard,model_print_payment_wizard,base.group_user,1,1,1,1
access_property_bills_list,property_bills_list,model_property_bills_list,base.group_user,1,1,1,1
access_property_bills_wizard,property_bills_wizard,model_property_bills_wizard,base.group_user,1,1,1,1
access_property_bills_wizard,property_bills_wizard,model_property_bills_wizard,base.group_user,1,1,1,1
access_action_refuse_wizard,action_refuse_wizard,model_action_refuse_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
121 access_action_refuse_wizard action_refuse_wizard model_action_refuse_wizard base.group_user 1 1 1 1
122
123
124
125
+2 -1
View File
@@ -13,4 +13,5 @@ from . import contract_signature_wizard
from . import build_pop_wizard
from . import penalty_line_wizard
from . import print_payment_wizard
from . import property_bills_wizard
from . import property_bills_wizard
from . import action_refuse_wizard
@@ -0,0 +1,51 @@
from odoo import models, fields, api, _, SUPERUSER_ID
from odoo.exceptions import UserError, ValidationError
from datetime import datetime, timedelta, date
from dateutil.relativedelta import relativedelta
class ActionRefuseWizard(models.TransientModel):
_name = 'action.refuse.wizard'
_description = '批量退回提醒弹窗'
receivable_ids = fields.Many2many('property.accounts.receivable', string='关联应收账单')
def confirm(self):
model_type_data = {
'水费': 'meter.water.line',
'电费': 'meter.archives.line',
'押金': 'property.deposit.list',
'租金': 'property.rent.list',
'管理费': 'property.management.list',
}
for rec in self.receivable_ids:
if rec.is_reconciliation == True:
continue
model_type = model_type_data.get(rec.management_type_id.name)
valid_management_types = list(model_type_data.values())
if model_type not in valid_management_types:
rec.unlink()
continue
if model_type in ('meter.archives.line', 'meter.water.line'):
if not rec.charging_range:
raise UserError("请输入计费区间, 按照应为'YYYY-MM-DD 到 YYYY-MM-DD格式")
charging_range = rec.charging_range.strip()
start_str, end_str = [x.strip() for x in charging_range.split("到")]
start_date = (datetime.strptime(end_str, "%Y-%m-%d").date()).replace(day=1)
end_date = datetime.strptime(end_str, "%Y-%m-%d").date()
model_id = self.env[model_type].search([
('contract_id', '=', rec.contract_id.id),
('meter_date', '>=', start_date),
('meter_date', '<=', end_date)
])
else:
last_day = rec.due_date + relativedelta(months=1) - timedelta(days=1)
model_id = self.env[model_type].search([
('contract_id', '=', rec.contract_id.id),
('due_date', '>=', rec.due_date),
('due_date', '<=', last_day)
])
if model_id:
model_id.write({'is_push': False})
rec.unlink()
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="action_refuse_wizard_form" model="ir.ui.view">
<field name="name">action.refuse.wizard.form</field>
<field name="model">action.refuse.wizard</field>
<field name="arch" type="xml">
<form>
<div>
<h1>
包含已对账账单,需要退回可先从银行流水徹销已对账信息,是否继续?
</h1>
</div>
<sheet>
<footer>
<button type="object" name="confirm" string="确认" class="oe_highlight"/>
<button special="cancel" string="取消" class="btn-secondary"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>
@@ -24,6 +24,7 @@ class ChoosePropertyWizard(models.Model):
lease_ids_to_add = []
title_deed = []
title_address = []
financial_address_list = []
for record in self.property_ids:
lease_name.append(record.name if record.name is not None else "")
title_addr_name = record.title_address.name if (record.title_address and record.title_address.name) else ""
@@ -50,12 +51,18 @@ class ChoosePropertyWizard(models.Model):
rec.admin_id = admin_id
rec.company_id = company_id
rec.precinct_id = precinct_id
fin_addr = record.financial_address if record.financial_address else ""
if fin_addr: # 仅添加非空地址
financial_address_list.append(fin_addr)
existing_names = []
for lease in self.lease_contract_id.lease_ids:
if lease.address_id:
name_val = lease.address_id.name
existing_names.append(name_val if name_val is not None else "无")
fin_addr_exist = lease.address_id.financial_address if lease.address_id.financial_address else ""
if fin_addr_exist:
financial_address_list.append(fin_addr_exist)
title_name = []
for lease in self.lease_contract_id.lease_ids:
@@ -63,9 +70,9 @@ class ChoosePropertyWizard(models.Model):
addr_name = lease.address_id.title_address.name if (lease.address_id.title_address and lease.address_id.title_address.name) else ""
title_name.append(str(addr_name))
# 过滤空字符串,避免拼接时出现空内容
all_names = [name for name in list(set(existing_names + lease_name)) if name]
title_address_combined = [addr for addr in list(set(title_address + title_name)) if addr]
use_address_combined = [addr for addr in list(set(financial_address_list)) if addr]
existing_lease_ids = [(4, lease.id) for lease in self.lease_contract_id.lease_ids]
@@ -77,7 +84,7 @@ class ChoosePropertyWizard(models.Model):
'title_address': ','.join(title_address_combined),
'lessor_id': self.lease_contract_id.lease_ids[0].address_id.company_id.id
if self.lease_contract_id.lease_ids else False,
'use_address': ','.join(all_names),
'use_address': ','.join(use_address_combined),
'address_for_delivery': ','.join(all_names),
'total_area': sum([lease.property_area for lease in rec.lease_ids])
})
+4 -3
View File
@@ -176,12 +176,10 @@ class YuthonWillTask(models.Model):
def add_comment(self):
"日程定时任务提醒"
now = fields.datetime.now() + timedelta(hours=8)
_logger.info(f'定时任务已执行:当前提醒时间:{now}')
dates_task_ids = self.env['yuthon.dates.task'].search([('task_state', '=', 'to_do'), ('reminder_time', '<', fields.datetime.now() + timedelta(days=1))])
for task in dates_task_ids:
hour1 = (task.reminder_time + timedelta(hours=8)).hour
minute1 = (task.reminder_time + timedelta(hours=8)).minute
_logger.info(f'任务实际小时:{hour1}:{minute1}\n 当前提醒小时:{now.hour}:{now.minute}')
if minute1 == now.minute and hour1 == now.hour:
task.write({'task_state': 'done'})
will_id = task.yuthon_will_task_id
@@ -248,7 +246,7 @@ class YuthonWillTask(models.Model):
reminder_employees = self.employee_ids | self.employee_ids2
if not reminder_employees:
return
old_tasks = self.env['yuthon.dates.task'].search([('yuthon_will_task_id', '=', self.id)])
old_tasks = self.env['yuthon.dates.task'].search([('yuthon_will_task_id', '=', self.id), ('task_state', '=', 'to_do')])
if old_tasks:
old_tasks.unlink()
start_hour = float(self.request_hour_from)
@@ -269,6 +267,9 @@ class YuthonWillTask(models.Model):
elif unit == '天':
reminder_hours = num * 24.0
reminder_datetime = task_start_datetime - timedelta(hours=reminder_hours)
now = fields.datetime.now() + timedelta(hours=8)
if now > reminder_datetime:
continue
existing_dates_task = self.env['yuthon.dates.task'].search([
('yuthon_will_task_id', '=', self.id),
('reminder_time', '=', reminder_datetime)
@@ -4,7 +4,10 @@
<field name="name">yuthon.dates.task.tree</field>
<field name="model">yuthon.dates.task</field>
<field name="arch" type="xml">
<tree string="日程任务" editable="bottom">
<tree string="日程任务" editable="bottom"
decoration-success="task_state == 'to_do'"
decoration-danger="task_state == 'done'"
>
<field name="yuthon_will_task_id"/>
<field name="employee_ids" widget="many2many_tags" options="{'no_create': True}"/>
<field name="reminder_time"/>
@@ -12,9 +15,24 @@
</tree>
</field>
</record>
<record id="view_yuthon_dates_task_search" model="ir.ui.view">
<field name="name">yuthon.dates.task.search</field>
<field name="model">yuthon.dates.task</field>
<field name="arch" type="xml">
<search string="日程任务">
<field name="yuthon_will_task_id"/>
<group expand="0" string="Group By">
<filter name="task_state" string="状态" context="{'group_by':'task_state'}"/>
</group>
</search>
</field>
</record>
<record id="view_yuthon_dates_task_action" model="ir.actions.act_window">
<field name="name">日程任务</field>
<field name="res_model">yuthon.dates.task</field>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_yuthon_dates_task_tree"/>
<field name="search_view_id" ref="view_yuthon_dates_task_search"/>
<field name="context">{'search_default_task_state':1}</field>
</record>
</odoo>
@@ -73,8 +73,8 @@
<group>
<label for="start_date"/>
<div class="d-inline-flex w-100">
<field name="start_date" string="日期:" class="me-2" style="max-width: 10rem !important;"/>时间
<field name="request_hour_from" string=" " class="ms-2" style="max-width: 5rem !important;"/>至
<field name="start_date" string="日期:" class="me-2" style="max-width: 10rem !important;"/>
<field name="request_hour_from" string=" " class="ms-2" style="max-width: 5rem !important;"/>→
<field name="request_hour_to" string=" " class="ms-2" style="max-width: 5rem !important;"/>
</div>
</group>
@@ -174,9 +174,9 @@
<label for="start_date"/>
<div class="d-inline-flex w-100">
<field name="start_date" string="日期:" class="me-2"
style="max-width: 10rem !important;"/>时间
style="max-width: 10rem !important;"/>
<field name="request_hour_from" string=" " class="ms-2"
style="max-width: 5rem !important;"/>至
style="max-width: 5rem !important;"/>→
<field name="request_hour_to" string=" " class="ms-2"
style="max-width: 5rem !important;"/>
</div>
@@ -255,7 +255,7 @@
<field name='name'>领导日程</field>
<field name='res_model'>yuthon.will.task</field>
<field name='view_mode'>tree,calendar,form</field>
<field name='domain'>[('user_role_ids.name', 'in', ['董事长', '分管领导', '总经理', '部门副经理', '副总经理'])]</field>
<field name='domain'>[('user_role_ids.name', 'in', ['董事长', '分管领导', '总经理', '部门副经理', '副总经理', '司机'])]</field>
<field name="context">{'search_default_next_seven_days': 1}</field>
</record>
</odoo>