添加功能

This commit is contained in:
李鹏宇
2025-12-28 11:59:44 +08:00
parent e7b2fae39c
commit 231df5d702
12 changed files with 272 additions and 121 deletions
@@ -2,7 +2,7 @@
import { Component } from "@odoo/owl";
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { deserializeDate } from "@web/core/l10n/dates";
import { serializeDateTime, serializeDate, deserializeDate, deserializeDateTime } from "@web/core/l10n/dates";
import { standardFieldFilterProps } from "../standard_field_filter_props.js"
export class DateFilter extends Component {
@@ -14,17 +14,23 @@ export class DateFilter extends Component {
this.endDate = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.endDate;
}
get dateFieldType() {
return this.props.type;
}
get dateFrom() {
return this.startDate ? deserializeDate(this.startDate): undefined;
const dateValue = this.startDate;
return dateValue ? (this.dateFieldType === "datetime" ? deserializeDateTime(dateValue) : deserializeDate(dateValue)) : undefined;
}
get dateTo() {
return this.endDate ? deserializeDate(this.endDate): undefined;
const dateValue = this.endDate;
return dateValue ? (this.dateFieldType === "datetime" ? deserializeDateTime(dateValue) : deserializeDate(dateValue)) : undefined;
}
// 设置筛选字段的值
setActiveValues(dateValue, type) {
const value = dateValue ? (this.props.type === "datetime" ? dateValue.toFormat("yyyy-MM-dd HH:mm:ss") : dateValue.toFormat("yyyy-MM-dd")) : undefined;
const value = dateValue ? (this.dateFieldType === "datetime" ? serializeDateTime(dateValue) : serializeDate(dateValue)) : undefined;
if (type === "startDate") {
this.startDate = value
}
@@ -61,13 +67,78 @@ export class DateFilter extends Component {
this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate});
}
setDateRange(months) {
const today = new Date();
const startDate = new Date();
startDate.setMonth(startDate.getMonth() - months);
this.startDate = startDate.toISOString().split('T')[0];
this.endDate = today.toISOString().split('T')[0];
this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate});
setDateRange(direction) {
// 只允许 next 或 previous
if (!["next", "previous"].includes(direction)) {
return;
}
// 月份偏移量
const monthOffset = direction === "next" ? 1 : -1;
// 当前时间(作为默认基准)
const now = luxon.DateTime.now();
// 反序列化开始时间
const start = this.startDate
? (this.dateFieldType === "datetime"
? deserializeDateTime(this.startDate)
: deserializeDate(this.startDate))
: null;
// 反序列化结束时间
const end = this.endDate
? (this.dateFieldType === "datetime"
? deserializeDateTime(this.endDate)
: deserializeDate(this.endDate))
: null;
// 情况一:开始和结束时间都不存在,默认使用当前月
if (!start && !end) {
const base = now.plus({ months: monthOffset });
// 当月开始
const monthStart = base.startOf("month");
// 当月结束
const monthEnd = base.endOf("month");
this.startDate = this.dateFieldType === "datetime"
? serializeDateTime(monthStart)
: serializeDate(monthStart);
this.endDate = this.dateFieldType === "datetime"
? serializeDateTime(monthEnd)
: serializeDate(monthEnd);
}
// 情况二:只存在开始时间
else if (start && !end) {
const newStart = start.plus({ months: monthOffset });
this.startDate = this.dateFieldType === "datetime"
? serializeDateTime(newStart)
: serializeDate(newStart);
}
// 情况三:只存在结束时间
else if (!start && end) {
const newEnd = end.plus({ months: monthOffset });
this.endDate = this.dateFieldType === "datetime"
? serializeDateTime(newEnd)
: serializeDate(newEnd);
}
// 情况四:开始和结束时间都存在
else {
const newStart = start.plus({ months: monthOffset });
const newEnd = end.plus({ months: monthOffset });
this.startDate = this.dateFieldType === "datetime"
? serializeDateTime(newStart)
: serializeDate(newStart);
this.endDate = this.dateFieldType === "datetime"
? serializeDateTime(newEnd)
: serializeDate(newEnd);
}
// 触发筛选条件更新
this.props.fieldOnchange({
fieldDomainList: this.domain,
startDate: this.startDate,
endDate: this.endDate,
});
}
// 点击清空筛选时触发
@@ -5,6 +5,9 @@
<t t-name="header_search_panel.DateFilter" owl="1">
<!-- 选择字段选择项 -->
<div class="date-filter">
<button class="filter-reset" type="button" t-on-click="() => this.setDateRange('previous')">
<i class="fa fa-lg fa-chevron-left"/>
</button>
<DateTimeInput value="dateFrom"
type="props.type"
onChange.bind="startDateOnchange"/>
@@ -12,26 +15,10 @@
<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"/> -->
<button class="filter-reset" type="button" t-on-click="() => this.setDateRange('next')">
<i class="fa fa-lg fa-chevron-right"/>
</button>
</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>
@@ -1,19 +1,17 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { Domain } from "@web/core/domain";
import {DateFilter} from "../fields_filter/date_filter/date_filter";
import { Component, useState } from "@odoo/owl";
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { serializeDateTime, serializeDate, deserializeDate, deserializeDateTime } from "@web/core/l10n/dates";
// 选择日期筛选,用于自己选择不同时间字段进行筛选
export class SelectDateFilter extends Component {
setup() {
super.setup();
// 默认选择第一个时间字段
this.dateField = this.props.dateFields[0];
// 开始时间
this.startDate = undefined;
// 结束时间
this.endDate = undefined;
this.state = useState({
dateField: this.env.searchModel.headerSearchInfo?.DateFilter?.fieldName || this.props.dateFields[0],
startDate: this.env.searchModel.headerSearchInfo?.DateFilter?.startDate,
endDate: this.env.searchModel.headerSearchInfo?.DateFilter?.endDate,
})
}
get dateFields() {
@@ -25,11 +23,29 @@ export class SelectDateFilter extends Component {
});
}
get dateFieldType() {
return this.env.searchModel.searchViewFields[this.state.dateField].type;
}
get dateFrom() {
const dateValue = this.state.startDate;
return dateValue ? (this.dateFieldType === "datetime" ? deserializeDateTime(dateValue) : deserializeDate(dateValue)) : undefined;
}
get dateTo() {
const dateValue = this.state.endDate;
return dateValue ? (this.dateFieldType === "datetime" ? deserializeDateTime(dateValue) : deserializeDate(dateValue)) : undefined;
}
// 设置筛选字段的值
setActiveValues(ev) {
const value = ev.target.value;
if (ev.target.id === "select_start_date") { this.startDate = value}
if (ev.target.id === "select_end_date"){ this.endDate = value}
setActiveValues(dateValue, type) {
const value = dateValue ? (this.dateFieldType === "datetime" ? serializeDateTime(dateValue) : serializeDate(dateValue)) : undefined;
if (type === "startDate") {
this.state.startDate = value
}
if (type === "endDate") {
this.state.endDate = value
}
}
get domain(){
@@ -37,49 +53,121 @@ export class SelectDateFilter extends Component {
// 时间筛选应该返回类似这样的domain
// ["&", ("create_date", ">=", "2024-05-22 16:00:00"), ("create_date", "<=", "2024-05-23 15:59:59")]
const domain = [];
if (this.startDate && this.endDate) {domain.push("&")}
if (this.startDate){domain.push([this.dateField, ">=", this.startDate])}
if (this.endDate){domain.push([this.dateField, "<=", this.endDate])}
if (this.state.startDate && this.state.endDate) {domain.push("&")}
if (this.state.startDate){domain.push([this.state.dateField, ">=", this.state.startDate])}
if (this.state.endDate){domain.push([this.state.dateField, "<=", this.state.endDate])}
return domain.length === 0? []: domain
}
// 选择时间字段变化
applySelectDateFilter() {
this.env.searchModel.setSelectDateFilter(
this.state.dateField,
this.state.startDate,
this.state.endDate,
this.domain
);
this.env.searchModel.applyHeaderFilters();
}
dateFieldOnchange(ev){
this.dateField = ev.target.value;
this.env.searchModel.setSelectDateFilter(this.dateField, this.domain);
this.env.searchModel.applyHeaderFilters();
this.state.dateField = ev.target.value;
this.state.startDate = undefined;
this.state.endDate = undefined;
this.applySelectDateFilter()
}
// 日期变化
dateOnchange(ev) {
this.setActiveValues(ev);
this.env.searchModel.setSelectDateFilter(this.dateField, this.domain);
this.env.searchModel.applyHeaderFilters();
startDateOnchange (startDate) {
this.setActiveValues(startDate, "startDate");
this.applySelectDateFilter()
}
setDateRange(months) {
const today = new Date();
const startDate = new Date();
startDate.setMonth(startDate.getMonth() - months);
this.startDate = startDate.toISOString().split('T')[0];
this.endDate = today.toISOString().split('T')[0];
this.env.searchModel.setSelectDateFilter(this.dateField, this.domain);
this.env.searchModel.applyHeaderFilters();
endDateOnchange (endDate) {
this.setActiveValues(endDate, "endDate");
this.applySelectDateFilter()
}
setDateRange(direction) {
// 只允许 next 或 previous
if (!["next", "previous"].includes(direction)) {
return;
}
// 月份偏移量
const monthOffset = direction === "next" ? 1 : -1;
// 当前时间(作为默认基准)
const now = luxon.DateTime.now();
// 反序列化开始时间
const start = this.state.startDate
? (this.dateFieldType === "datetime"
? deserializeDateTime(this.state.startDate)
: deserializeDate(this.state.startDate))
: null;
// 反序列化结束时间
const end = this.state.endDate
? (this.dateFieldType === "datetime"
? deserializeDateTime(this.state.endDate)
: deserializeDate(this.state.endDate))
: null;
// 情况一:开始和结束时间都不存在,默认使用当前月
if (!start && !end) {
const base = now.plus({ months: monthOffset });
// 当月开始
const monthStart = base.startOf("month");
// 当月结束
const monthEnd = base.endOf("month");
this.state.startDate = this.dateFieldType === "datetime"
? serializeDateTime(monthStart)
: serializeDate(monthStart);
this.state.endDate = this.dateFieldType === "datetime"
? serializeDateTime(monthEnd)
: serializeDate(monthEnd);
}
// 情况二:只存在开始时间
else if (start && !end) {
const newStart = start.plus({ months: monthOffset });
this.state.startDate = this.dateFieldType === "datetime"
? serializeDateTime(newStart)
: serializeDate(newStart);
}
// 情况三:只存在结束时间
else if (!start && end) {
const newEnd = end.plus({ months: monthOffset });
this.state.endDate = this.dateFieldType === "datetime"
? serializeDateTime(newEnd)
: serializeDate(newEnd);
}
// 情况四:开始和结束时间都存在
else {
const newStart = start.plus({ months: monthOffset });
const newEnd = end.plus({ months: monthOffset });
this.state.startDate = this.dateFieldType === "datetime"
? serializeDateTime(newStart)
: serializeDate(newStart);
this.state.endDate = this.dateFieldType === "datetime"
? serializeDateTime(newEnd)
: serializeDate(newEnd);
}
this.applySelectDateFilter()
}
// 点击重置按钮
resetFilter() {
this.dateField = this.props.dateFields[0];
this.startDate = undefined;
this.endDate = undefined;
this.state.dateField = this.props.dateFields[0];
this.state.startDate = undefined;
this.state.endDate = undefined;
// 重新渲染
this.env.searchModel.setSelectDateFilter(this.dateField, this.domain);
this.env.searchModel.applyHeaderFilters();
this.applySelectDateFilter()
}
}
SelectDateFilter.template = "header_search_panel.SelectDateFilter";
SelectDateFilter.components = {
DateTimeInput,
};
SelectDateFilter.props = {
// 时间字段列表
dateFields: { type: Array, required: true },
@@ -5,10 +5,10 @@
<t t-name="header_search_panel.SelectDateFilter" owl="1">
<!-- 自选时间字段 -->
<div class="header-filter-label">
<div class="header-search-select">
<select name="select_date_field" t-on-change="dateFieldOnchange">
<div>
<select class="select-date-field" name="select_date_field" t-on-change="dateFieldOnchange">
<!-- 遍历允许的时间字段 -->
<option t-foreach="dateFields" t-as="field" t-att-value="field.name" t-key="field_index" t-att-selected="field.name === dateField">
<option t-foreach="dateFields" t-as="field" t-att-value="field.name" t-key="field_index" t-att-selected="field.name === state.dateField">
<span><t t-esc="field.string"/></span>
</option>
</select>
@@ -18,26 +18,20 @@
<div class="header-filter-value">
<!-- 选择字段选择项 -->
<div class="date-filter">
<input
type="date"
id="select_start_date"
name="start_date"
t-att-value="startDate"
t-on-change="dateOnchange"/>
<input
type="date"
id="select_end_date"
name="end_date"
t-att-value="endDate"
t-on-change="dateOnchange"/>
<button class="filter-reset" type="button" t-on-click="() => this.setDateRange('previous')">
<i class="fa fa-lg fa-chevron-left"/>
</button>
<DateTimeInput value="dateFrom"
type="dateFieldType"
onChange.bind="startDateOnchange"/>
<i class="oi oi-arrow-right"/>
<DateTimeInput value="dateTo"
type="dateFieldType"
onChange.bind="endDateOnchange"/>
<button class="filter-reset" type="button" t-on-click="() => this.setDateRange('next')">
<i class="fa fa-lg fa-chevron-right"/>
</button>
</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>
<button class="filter-reset" type="button" aria-label="重置筛选" t-on-click="resetFilter">
<i class="fa fa-close fa-lg"/>
</button>
@@ -33,34 +33,13 @@
padding-right: 10px;
font-weight: bold;
.header-search-select {
display: flex;
align-items: center;
height: 100%;
position: relative;
border: 1px solid #ccc; /* 设置边框 */
border-radius: 0.5rem; /* 设置边框圆角 */
overflow: hidden; /* 防止溢出 */
background-color: #fff; /* 设置背景颜色 */
color: #333; /* 设置文字颜色 */
select {
text-align: right;
margin-right: 2rem; /* 设置右侧的距离,预留出下拉箭头的距离 */
margin-left: 1rem;
font-weight: bold; /* 设置字体加粗 */
border: none; /* 移除默认边框 */
background: transparent; /* 透明背景 */
outline: none; /* 移除默认焦点样式 */
appearance: none; /* 移除默认样式,如箭头 */
}
}
.header-search-select::after {
content: '▼'; /* 下拉箭头字符 */
position: absolute;
top: 50%;
right: 0.5rem; /* 设置箭头与右侧的距离 */
transform: translateY(-50%); /* 垂直居中 */
.select-date-field {
padding: 0.1rem 1rem;
border: 0.1rem solid $o-community-color;
border-radius: 0.3rem;
background-color: transparent;
background-position: right 10px center;
color: $o-community-color;
}
}
@@ -80,6 +59,8 @@
.o_datetime_input,
.fast-date-filter a {
width: auto;
min-width: 230px;
text-align: center;
margin: 0.2rem;
padding: 0.1rem 1rem;
border: 0.1rem solid $o-community-color; /* 将边框颜色改为黑色 */
@@ -158,7 +158,11 @@ patch(SearchModel.prototype, {
},
// 设置自选时间筛选
setSelectDateFilter(fieldName, fieldDomain) {
setSelectDateFilter(fieldName, startDate, endDate, fieldDomain) {
// 设置日期筛选相关属性
this.headerSearchInfo.DateFilter.fieldName = fieldName;
this.headerSearchInfo.DateFilter.startDate = startDate;
this.headerSearchInfo.DateFilter.endDate = endDate;
this.headerSearchInfo.DateFilter.fieldDomain = fieldDomain;
},
@@ -57,6 +57,10 @@
<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>
<header_search>
<field name="name"/>
<field name="code"/>
</header_search>
</search>
</field>
</record>
@@ -9,6 +9,7 @@ class RoomBooking(models.Model):
is_issue = fields.Boolean(string="是否关联议题", default=True)
issue_meeting_line_ids = fields.One2many('issue.meeting.line', 'meeting_booking_id', string="关联议题明细行")
number_of_meetings = fields.Integer(string='会议次数')
minutes_id = fields.Many2one('issue.meeting.minutes', string='会议纪要流程')
@api.constrains('start_datetime')
def _check_start_datetime(self):
@@ -21,6 +22,7 @@ class RoomBooking(models.Model):
if meet.issue_library_id.is_boss == True:
meet.issue_library_id.boss_date = self.start_datetime + timedelta(hours=8)
def bottom_meeting_approval(self):
library_data = []
for library in self.issue_meeting_line_ids:
@@ -39,6 +41,8 @@ class RoomBooking(models.Model):
'res_model': 'issue.meeting.minutes',
'context': {
'default_meeting_id': self.id,
'default_room_users_ids': [(6, 0, self.room_users_ids.ids)],
'default_host': self.host.id,
'default_meeting_type_id': self.meeting_type_id.id,
'default_meeting_date': self.start_datetime,
'default_number_of_meetings': self.number_of_meetings,
@@ -15,6 +15,8 @@ class IssueMeetingMinutes(models.Model):
meeting_id = fields.Many2one('room.booking', string='会议名称', required=True)
meeting_type_id = fields.Many2one('room.meeting.type', string="会议类型")
meeting_date = fields.Datetime(string="会议时间")
room_users_ids = fields.Many2many('res.users', string="出席人员")
host = fields.Many2one('hr.employee', string="主持人")
code = fields.Char(string='编号', required=True, copy=False,
default=lambda self: self.env['ir.sequence'].next_by_code('issue_meeting_minutes_code'))
meeting_minutes_ids = fields.One2many('issue.meeting.minutes.line', 'meeting_minutes_id', string="会议纪要明细")
@@ -41,6 +43,13 @@ class IssueMeetingMinutes(models.Model):
else:
record.computed_meeting_name = False
@api.constrains('meeting_id')
def _check_meeting_id(self):
for record in self:
if record.meeting_id:
record.meeting_id.minutes_id = self.id
class IssueMeetingMinutesLine(models.Model):
_name = 'issue.meeting.minutes.line'
_description = '会议纪要明细行'
+7 -1
View File
@@ -11,6 +11,13 @@
<xpath expr="//field[@name='meeting_place']" position="after">
<field name="number_of_meetings"/>
</xpath>
<xpath expr="//field[@name='meeting_type_id']" position="after">
<field name="minutes_id" invisible="not minutes_id"/>
</xpath>
<xpath expr="//button[@name='sync_wechat_room']" position="after">
<button name="bottom_meeting_approval" string="会议纪要" type="object" class="oe_highlight"/>
</xpath>
<xpath expr="//page[@name='sign']" position="before">
<page string="关联议题" invisible="not is_issue">
<field name="issue_meeting_line_ids">
@@ -24,7 +31,6 @@
<field name="decision_details"/>
</tree>
</field>
<button name="bottom_meeting_approval" string="提交会议纪要审批单" type="object" class="oe_highlight"/>
</page>
</xpath>
</field>
@@ -43,6 +43,8 @@
<field name="name"/>
<field name="work_end" invisible="1"/>
<field name="meeting_id"/>
<field name="host"/>
<field name="room_users_ids" widget="many2many_tags"/>
<field name="meeting_date"/>
<field name="meeting_type_id"/>
</group>
@@ -109,6 +109,7 @@ class InheritPurchaseOrderLine(models.Model):
PURCHASE_TYPE_MAP = {
'fixed': '固定资产',
'low': '低值易耗',
'service': '服务',
'book': '书籍报刊',
'foot': '饭堂食材'
}