From 7cff04e0de33e09946c2b9ed143082d70f47fe07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 19 Dec 2025 15:24:31 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ => header_search_panel}/README.md | 19 +- .../{ => header_search_panel}/__init__.py | 0 .../{ => header_search_panel}/__manifest__.py | 0 .../override_relaxng.py | 0 .../rng/search_view.rng | 0 .../static/src/legacy/backend_utils.js | 33 +++ .../src/search/control_panel/control_panel.js | 0 .../search/control_panel/control_panel.scss | 0 .../search/control_panel/control_panel.xml | 0 .../boolean_filter/boolean_filter.js | 0 .../boolean_filter/boolean_filter.xml | 0 .../fields_filter/char_filter/char_filter.js | 54 +++++ .../fields_filter/char_filter/char_filter.xml | 15 ++ .../fields_filter/date_filter/date_filter.js | 37 ++- .../fields_filter/date_filter/date_filter.xml | 37 +++ .../components/fields_filter/fields_filter.js | 15 ++ .../fields_filter/fields_filter.xml | 0 .../many2one_filter/many2one_filter.js | 1 + .../many2one_filter/many2one_filter.xml | 0 .../selection_filter/selection_filter.js | 0 .../selection_filter/selection_filter.xml | 0 .../standard_field_filter_props.js | 2 + .../x2many_filter/x2many_filter.js | 0 .../x2many_filter/x2many_filter.xml | 0 .../components/quick_filter/quick_filter.js | 0 .../components/quick_filter/quick_filter.xml | 4 +- .../quick_filter/quick_filter_label.scss | 0 .../select_date_filter/select_date_filter.js | 0 .../select_date_filter/select_date_filter.xml | 0 .../header_search/header_search_filter.js | 0 .../header_search/header_search_filter.scss | 14 +- .../header_search/header_search_filter.xml | 0 .../static/src/search/search_arch_parser.js | 14 +- .../static/src/search/search_model.js | 0 header_search_panel/model_test/__init__.py | 4 + .../model_test/__manifest__.py | 21 ++ .../model_test/controllers/__init__.py | 3 + .../model_test/controllers/controllers.py | 21 ++ .../model_test/models/__init__.py | 3 + .../model_test/models/models.py | 211 ++++++++++++++++++ .../model_test/security/ir.model.access.csv | 3 + .../model_test/static/src/model_test_list.xml | 17 ++ .../static/src/model_test_list_controller.js | 10 + .../model_test/views/views.xml | 126 +++++++++++ .../fields_filter/date_filter/date_filter.xml | 30 --- 45 files changed, 636 insertions(+), 58 deletions(-) rename header_search_panel/{ => header_search_panel}/README.md (79%) rename header_search_panel/{ => header_search_panel}/__init__.py (100%) rename header_search_panel/{ => header_search_panel}/__manifest__.py (100%) rename header_search_panel/{ => header_search_panel}/override_relaxng.py (100%) rename header_search_panel/{ => header_search_panel}/rng/search_view.rng (100%) create mode 100644 header_search_panel/header_search_panel/static/src/legacy/backend_utils.js rename header_search_panel/{ => header_search_panel}/static/src/search/control_panel/control_panel.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/control_panel/control_panel.scss (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/control_panel/control_panel.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml (100%) create mode 100644 header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js create mode 100644 header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js (65%) create mode 100644 header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/fields_filter.js (93%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/fields_filter.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js (98%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js (88%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/quick_filter/quick_filter.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/quick_filter/quick_filter.xml (92%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/quick_filter/quick_filter_label.scss (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/select_date_filter/select_date_filter.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/components/select_date_filter/select_date_filter.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/header_search_filter.js (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/header_search_filter.scss (96%) rename header_search_panel/{ => header_search_panel}/static/src/search/header_search/header_search_filter.xml (100%) rename header_search_panel/{ => header_search_panel}/static/src/search/search_arch_parser.js (96%) rename header_search_panel/{ => header_search_panel}/static/src/search/search_model.js (100%) create mode 100644 header_search_panel/model_test/__init__.py create mode 100644 header_search_panel/model_test/__manifest__.py create mode 100644 header_search_panel/model_test/controllers/__init__.py create mode 100644 header_search_panel/model_test/controllers/controllers.py create mode 100644 header_search_panel/model_test/models/__init__.py create mode 100644 header_search_panel/model_test/models/models.py create mode 100644 header_search_panel/model_test/security/ir.model.access.csv create mode 100644 header_search_panel/model_test/static/src/model_test_list.xml create mode 100644 header_search_panel/model_test/static/src/model_test_list_controller.js create mode 100644 header_search_panel/model_test/views/views.xml delete mode 100644 header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml diff --git a/header_search_panel/README.md b/header_search_panel/header_search_panel/README.md similarity index 79% rename from header_search_panel/README.md rename to header_search_panel/header_search_panel/README.md index 5339e4749..9e9d7b0bf 100644 --- a/header_search_panel/README.md +++ b/header_search_panel/header_search_panel/README.md @@ -6,7 +6,7 @@ - 快捷筛选(quick_filter) - 自选时间筛选(select_date_filter)(待移植) -- 字段搜索(field)(待移植) +- 字段搜索(field)(已移植) 通过这些筛选标签,用户可以更灵活地对列表视图中的数据进行筛选。 @@ -20,9 +20,9 @@ #### 支持的属性: -| 属性名 | 类型 | 说明 | 是否可选 | -| ------- | ------ | ---------- | ---- | -| `class` | String | 自定义 CSS 类名 | ✅ | +| 属性名 | 类型 | 说明 | 是否可选 | +| ------- | ------ |-------------------------------------------------------------------| ---- | +| `class` | String | 自定义 CSS 类名,可使用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`: 不显示标题 | | #### 示例: diff --git a/header_search_panel/__init__.py b/header_search_panel/header_search_panel/__init__.py similarity index 100% rename from header_search_panel/__init__.py rename to header_search_panel/header_search_panel/__init__.py diff --git a/header_search_panel/__manifest__.py b/header_search_panel/header_search_panel/__manifest__.py similarity index 100% rename from header_search_panel/__manifest__.py rename to header_search_panel/header_search_panel/__manifest__.py diff --git a/header_search_panel/override_relaxng.py b/header_search_panel/header_search_panel/override_relaxng.py similarity index 100% rename from header_search_panel/override_relaxng.py rename to header_search_panel/header_search_panel/override_relaxng.py diff --git a/header_search_panel/rng/search_view.rng b/header_search_panel/header_search_panel/rng/search_view.rng similarity index 100% rename from header_search_panel/rng/search_view.rng rename to header_search_panel/header_search_panel/rng/search_view.rng diff --git a/header_search_panel/header_search_panel/static/src/legacy/backend_utils.js b/header_search_panel/header_search_panel/static/src/legacy/backend_utils.js new file mode 100644 index 000000000..6f77264e2 --- /dev/null +++ b/header_search_panel/header_search_panel/static/src/legacy/backend_utils.js @@ -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 +// }, +// }) \ No newline at end of file diff --git a/header_search_panel/static/src/search/control_panel/control_panel.js b/header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.js similarity index 100% rename from header_search_panel/static/src/search/control_panel/control_panel.js rename to header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.js diff --git a/header_search_panel/static/src/search/control_panel/control_panel.scss b/header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.scss similarity index 100% rename from header_search_panel/static/src/search/control_panel/control_panel.scss rename to header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.scss diff --git a/header_search_panel/static/src/search/control_panel/control_panel.xml b/header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.xml similarity index 100% rename from header_search_panel/static/src/search/control_panel/control_panel.xml rename to header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.xml diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js new file mode 100644 index 000000000..80cceca3a --- /dev/null +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js @@ -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, +}; diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml new file mode 100644 index 000000000..66f439179 --- /dev/null +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js similarity index 65% rename from header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js index e72c40f44..d27ef8ff0 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js @@ -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, }; diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml new file mode 100644 index 000000000..bf86c13a4 --- /dev/null +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js similarity index 93% rename from header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js index c70c0878c..0295ec005 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js @@ -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){ diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js similarity index 98% rename from header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js index 1c80a1a04..4db0404ab 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js @@ -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, }; } } diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js similarity index 88% rename from header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js index 231ebf6b4..2fa7705c2 100644 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js @@ -13,4 +13,6 @@ export const standardFieldFilterProps = { type: {type: String, required: true}, // 字段变换监听函数 fieldOnchange: { type: Function, required: true }, + // 提示 + placeholder: {type: String, optional: true}, }; diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml diff --git a/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js similarity index 100% rename from header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js diff --git a/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml similarity index 92% rename from header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml index 17da74122..7ea0d8bbe 100644 --- a/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml +++ b/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml @@ -2,11 +2,11 @@ - + diff --git a/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss b/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss similarity index 100% rename from header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss rename to header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss diff --git a/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js similarity index 100% rename from header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js diff --git a/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml diff --git a/header_search_panel/static/src/search/header_search/header_search_filter.js b/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.js similarity index 100% rename from header_search_panel/static/src/search/header_search/header_search_filter.js rename to header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.js diff --git a/header_search_panel/static/src/search/header_search/header_search_filter.scss b/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.scss similarity index 96% rename from header_search_panel/static/src/search/header_search/header_search_filter.scss rename to header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.scss index 32a658282..26091f318 100644 --- a/header_search_panel/static/src/search/header_search/header_search_filter.scss +++ b/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.scss @@ -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; } -} \ No newline at end of file +} diff --git a/header_search_panel/static/src/search/header_search/header_search_filter.xml b/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.xml similarity index 100% rename from header_search_panel/static/src/search/header_search/header_search_filter.xml rename to header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.xml diff --git a/header_search_panel/static/src/search/search_arch_parser.js b/header_search_panel/header_search_panel/static/src/search/search_arch_parser.js similarity index 96% rename from header_search_panel/static/src/search/search_arch_parser.js rename to header_search_panel/header_search_panel/static/src/search/search_arch_parser.js index eaa2fddb4..e53f615e1 100644 --- a/header_search_panel/static/src/search/search_arch_parser.js +++ b/header_search_panel/header_search_panel/static/src/search/search_arch_parser.js @@ -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; diff --git a/header_search_panel/static/src/search/search_model.js b/header_search_panel/header_search_panel/static/src/search/search_model.js similarity index 100% rename from header_search_panel/static/src/search/search_model.js rename to header_search_panel/header_search_panel/static/src/search/search_model.js diff --git a/header_search_panel/model_test/__init__.py b/header_search_panel/model_test/__init__.py new file mode 100644 index 000000000..511a0ca3a --- /dev/null +++ b/header_search_panel/model_test/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models \ No newline at end of file diff --git a/header_search_panel/model_test/__manifest__.py b/header_search_panel/model_test/__manifest__.py new file mode 100644 index 000000000..42eb43c94 --- /dev/null +++ b/header_search_panel/model_test/__manifest__.py @@ -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/**/*', + ], + }, +} diff --git a/header_search_panel/model_test/controllers/__init__.py b/header_search_panel/model_test/controllers/__init__.py new file mode 100644 index 000000000..457bae27e --- /dev/null +++ b/header_search_panel/model_test/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers \ No newline at end of file diff --git a/header_search_panel/model_test/controllers/controllers.py b/header_search_panel/model_test/controllers/controllers.py new file mode 100644 index 000000000..22c44a3ab --- /dev/null +++ b/header_search_panel/model_test/controllers/controllers.py @@ -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/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('file_preview_test.object', { +# 'object': obj +# }) diff --git a/header_search_panel/model_test/models/__init__.py b/header_search_panel/model_test/models/__init__.py new file mode 100644 index 000000000..5305644df --- /dev/null +++ b/header_search_panel/model_test/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models \ No newline at end of file diff --git a/header_search_panel/model_test/models/models.py b/header_search_panel/model_test/models/models.py new file mode 100644 index 000000000..a6b58a725 --- /dev/null +++ b/header_search_panel/model_test/models/models.py @@ -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) diff --git a/header_search_panel/model_test/security/ir.model.access.csv b/header_search_panel/model_test/security/ir.model.access.csv new file mode 100644 index 000000000..583335d26 --- /dev/null +++ b/header_search_panel/model_test/security/ir.model.access.csv @@ -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 \ No newline at end of file diff --git a/header_search_panel/model_test/static/src/model_test_list.xml b/header_search_panel/model_test/static/src/model_test_list.xml new file mode 100644 index 000000000..c80232c10 --- /dev/null +++ b/header_search_panel/model_test/static/src/model_test_list.xml @@ -0,0 +1,17 @@ + + + + + + + 方案1 + + + 方案2 + + + + + \ No newline at end of file diff --git a/header_search_panel/model_test/static/src/model_test_list_controller.js b/header_search_panel/model_test/static/src/model_test_list_controller.js new file mode 100644 index 000000000..e95ce7236 --- /dev/null +++ b/header_search_panel/model_test/static/src/model_test_list_controller.js @@ -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); diff --git a/header_search_panel/model_test/views/views.xml b/header_search_panel/model_test/views/views.xml new file mode 100644 index 000000000..c5c99e1c5 --- /dev/null +++ b/header_search_panel/model_test/views/views.xml @@ -0,0 +1,126 @@ + + + + + + model.test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + model.test.tree + model.test + + + + + + + + + + + + + + + + + + + model.test.form + model.test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 文件测试 + main + model.test + tree,form + + + + + + + \ No newline at end of file diff --git a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml deleted file mode 100644 index 707547669..000000000 --- a/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - 近1个月 - 近3个月 - 近半年 - 近1年 - 近2年 - - - - From 72f0da291037e3552ebca95bbdb27653f4cfb21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 19 Dec 2025 15:37:59 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{header_search_panel => }/README.md | 0 .../{header_search_panel => }/__init__.py | 0 .../{header_search_panel => }/__manifest__.py | 0 header_search_panel/model_test/__init__.py | 4 - .../model_test/__manifest__.py | 21 -- .../model_test/controllers/__init__.py | 3 - .../model_test/controllers/controllers.py | 21 -- .../model_test/models/__init__.py | 3 - .../model_test/models/models.py | 211 ------------------ .../model_test/security/ir.model.access.csv | 3 - .../model_test/static/src/model_test_list.xml | 17 -- .../static/src/model_test_list_controller.js | 10 - .../model_test/views/views.xml | 126 ----------- .../override_relaxng.py | 0 .../rng/search_view.rng | 0 .../static/src/legacy/backend_utils.js | 0 .../src/search/control_panel/control_panel.js | 0 .../search/control_panel/control_panel.scss | 0 .../search/control_panel/control_panel.xml | 0 .../boolean_filter/boolean_filter.js | 0 .../boolean_filter/boolean_filter.xml | 0 .../fields_filter/char_filter/char_filter.js | 0 .../fields_filter/char_filter/char_filter.xml | 0 .../fields_filter/date_filter/date_filter.js | 0 .../fields_filter/date_filter/date_filter.xml | 0 .../components/fields_filter/fields_filter.js | 0 .../fields_filter/fields_filter.xml | 0 .../many2one_filter/many2one_filter.js | 0 .../many2one_filter/many2one_filter.xml | 0 .../selection_filter/selection_filter.js | 0 .../selection_filter/selection_filter.xml | 0 .../standard_field_filter_props.js | 0 .../x2many_filter/x2many_filter.js | 0 .../x2many_filter/x2many_filter.xml | 0 .../components/quick_filter/quick_filter.js | 0 .../components/quick_filter/quick_filter.xml | 0 .../quick_filter/quick_filter_label.scss | 0 .../select_date_filter/select_date_filter.js | 0 .../select_date_filter/select_date_filter.xml | 0 .../header_search/header_search_filter.js | 0 .../header_search/header_search_filter.scss | 0 .../header_search/header_search_filter.xml | 0 .../static/src/search/search_arch_parser.js | 0 .../static/src/search/search_model.js | 0 44 files changed, 419 deletions(-) rename header_search_panel/{header_search_panel => }/README.md (100%) rename header_search_panel/{header_search_panel => }/__init__.py (100%) rename header_search_panel/{header_search_panel => }/__manifest__.py (100%) delete mode 100644 header_search_panel/model_test/__init__.py delete mode 100644 header_search_panel/model_test/__manifest__.py delete mode 100644 header_search_panel/model_test/controllers/__init__.py delete mode 100644 header_search_panel/model_test/controllers/controllers.py delete mode 100644 header_search_panel/model_test/models/__init__.py delete mode 100644 header_search_panel/model_test/models/models.py delete mode 100644 header_search_panel/model_test/security/ir.model.access.csv delete mode 100644 header_search_panel/model_test/static/src/model_test_list.xml delete mode 100644 header_search_panel/model_test/static/src/model_test_list_controller.js delete mode 100644 header_search_panel/model_test/views/views.xml rename header_search_panel/{header_search_panel => }/override_relaxng.py (100%) rename header_search_panel/{header_search_panel => }/rng/search_view.rng (100%) rename header_search_panel/{header_search_panel => }/static/src/legacy/backend_utils.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/control_panel/control_panel.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/control_panel/control_panel.scss (100%) rename header_search_panel/{header_search_panel => }/static/src/search/control_panel/control_panel.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/fields_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/fields_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/quick_filter/quick_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/quick_filter/quick_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/quick_filter/quick_filter_label.scss (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/select_date_filter/select_date_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/components/select_date_filter/select_date_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/header_search_filter.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/header_search_filter.scss (100%) rename header_search_panel/{header_search_panel => }/static/src/search/header_search/header_search_filter.xml (100%) rename header_search_panel/{header_search_panel => }/static/src/search/search_arch_parser.js (100%) rename header_search_panel/{header_search_panel => }/static/src/search/search_model.js (100%) diff --git a/header_search_panel/header_search_panel/README.md b/header_search_panel/README.md similarity index 100% rename from header_search_panel/header_search_panel/README.md rename to header_search_panel/README.md diff --git a/header_search_panel/header_search_panel/__init__.py b/header_search_panel/__init__.py similarity index 100% rename from header_search_panel/header_search_panel/__init__.py rename to header_search_panel/__init__.py diff --git a/header_search_panel/header_search_panel/__manifest__.py b/header_search_panel/__manifest__.py similarity index 100% rename from header_search_panel/header_search_panel/__manifest__.py rename to header_search_panel/__manifest__.py diff --git a/header_search_panel/model_test/__init__.py b/header_search_panel/model_test/__init__.py deleted file mode 100644 index 511a0ca3a..000000000 --- a/header_search_panel/model_test/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers -from . import models \ No newline at end of file diff --git a/header_search_panel/model_test/__manifest__.py b/header_search_panel/model_test/__manifest__.py deleted file mode 100644 index 42eb43c94..000000000 --- a/header_search_panel/model_test/__manifest__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- 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/**/*', - ], - }, -} diff --git a/header_search_panel/model_test/controllers/__init__.py b/header_search_panel/model_test/controllers/__init__.py deleted file mode 100644 index 457bae27e..000000000 --- a/header_search_panel/model_test/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file diff --git a/header_search_panel/model_test/controllers/controllers.py b/header_search_panel/model_test/controllers/controllers.py deleted file mode 100644 index 22c44a3ab..000000000 --- a/header_search_panel/model_test/controllers/controllers.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- 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/', auth='public') -# def object(self, obj, **kw): -# return http.request.render('file_preview_test.object', { -# 'object': obj -# }) diff --git a/header_search_panel/model_test/models/__init__.py b/header_search_panel/model_test/models/__init__.py deleted file mode 100644 index 5305644df..000000000 --- a/header_search_panel/model_test/models/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import models \ No newline at end of file diff --git a/header_search_panel/model_test/models/models.py b/header_search_panel/model_test/models/models.py deleted file mode 100644 index a6b58a725..000000000 --- a/header_search_panel/model_test/models/models.py +++ /dev/null @@ -1,211 +0,0 @@ -# -*- 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) diff --git a/header_search_panel/model_test/security/ir.model.access.csv b/header_search_panel/model_test/security/ir.model.access.csv deleted file mode 100644 index 583335d26..000000000 --- a/header_search_panel/model_test/security/ir.model.access.csv +++ /dev/null @@ -1,3 +0,0 @@ -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 \ No newline at end of file diff --git a/header_search_panel/model_test/static/src/model_test_list.xml b/header_search_panel/model_test/static/src/model_test_list.xml deleted file mode 100644 index c80232c10..000000000 --- a/header_search_panel/model_test/static/src/model_test_list.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - 方案1 - - - 方案2 - - - - - \ No newline at end of file diff --git a/header_search_panel/model_test/static/src/model_test_list_controller.js b/header_search_panel/model_test/static/src/model_test_list_controller.js deleted file mode 100644 index e95ce7236..000000000 --- a/header_search_panel/model_test/static/src/model_test_list_controller.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @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); diff --git a/header_search_panel/model_test/views/views.xml b/header_search_panel/model_test/views/views.xml deleted file mode 100644 index c5c99e1c5..000000000 --- a/header_search_panel/model_test/views/views.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - model.test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - model.test.tree - model.test - - - - - - - - - - - - - - - - - - - model.test.form - model.test - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 文件测试 - main - model.test - tree,form - - - - - - - \ No newline at end of file diff --git a/header_search_panel/header_search_panel/override_relaxng.py b/header_search_panel/override_relaxng.py similarity index 100% rename from header_search_panel/header_search_panel/override_relaxng.py rename to header_search_panel/override_relaxng.py diff --git a/header_search_panel/header_search_panel/rng/search_view.rng b/header_search_panel/rng/search_view.rng similarity index 100% rename from header_search_panel/header_search_panel/rng/search_view.rng rename to header_search_panel/rng/search_view.rng diff --git a/header_search_panel/header_search_panel/static/src/legacy/backend_utils.js b/header_search_panel/static/src/legacy/backend_utils.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/legacy/backend_utils.js rename to header_search_panel/static/src/legacy/backend_utils.js diff --git a/header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.js b/header_search_panel/static/src/search/control_panel/control_panel.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.js rename to header_search_panel/static/src/search/control_panel/control_panel.js diff --git a/header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.scss b/header_search_panel/static/src/search/control_panel/control_panel.scss similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.scss rename to header_search_panel/static/src/search/control_panel/control_panel.scss diff --git a/header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.xml b/header_search_panel/static/src/search/control_panel/control_panel.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/control_panel/control_panel.xml rename to header_search_panel/static/src/search/control_panel/control_panel.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/boolean_filter/boolean_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/char_filter/char_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/date_filter/date_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/fields_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/many2one_filter/many2one_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/selection_filter/selection_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js b/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/standard_field_filter_props.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js b/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js rename to header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml b/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml rename to header_search_panel/static/src/search/header_search/components/fields_filter/x2many_filter/x2many_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js b/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js rename to header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml b/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml rename to header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss b/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss rename to header_search_panel/static/src/search/header_search/components/quick_filter/quick_filter_label.scss diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js b/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js rename to header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml b/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml rename to header_search_panel/static/src/search/header_search/components/select_date_filter/select_date_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.js b/header_search_panel/static/src/search/header_search/header_search_filter.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.js rename to header_search_panel/static/src/search/header_search/header_search_filter.js diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.scss b/header_search_panel/static/src/search/header_search/header_search_filter.scss similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.scss rename to header_search_panel/static/src/search/header_search/header_search_filter.scss diff --git a/header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.xml b/header_search_panel/static/src/search/header_search/header_search_filter.xml similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/header_search/header_search_filter.xml rename to header_search_panel/static/src/search/header_search/header_search_filter.xml diff --git a/header_search_panel/header_search_panel/static/src/search/search_arch_parser.js b/header_search_panel/static/src/search/search_arch_parser.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/search_arch_parser.js rename to header_search_panel/static/src/search/search_arch_parser.js diff --git a/header_search_panel/header_search_panel/static/src/search/search_model.js b/header_search_panel/static/src/search/search_model.js similarity index 100% rename from header_search_panel/header_search_panel/static/src/search/search_model.js rename to header_search_panel/static/src/search/search_model.js From 94b03c72d9e09ec6c497c779df304e856ecc13a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 19 Dec 2025 16:54:44 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../property_accounts_receivable_views.xml | 39 ++ yuthon_property_approval/__manifest__.py | 1 + .../models/inherit_yuthon_property.py | 287 ++++++-- .../security/ir.model.access.csv | 3 +- .../views/inherit_yuthon_property_views.xml | 46 ++ yuthon_property_approval/wizard/__init__.py | 3 +- .../wizard/property_deposit_wizard.py | 648 ++++++++++++++++++ .../wizard/property_deposit_wizard_views.xml | 65 ++ .../property_reconciliation_wizard_views.xml | 3 +- .../security/salary_security_rule.xml | 5 + yuthon_work_brand/views/hr_menus.xml | 10 +- 11 files changed, 1045 insertions(+), 65 deletions(-) create mode 100644 yuthon_property_approval/wizard/property_deposit_wizard.py create mode 100644 yuthon_property_approval/wizard/property_deposit_wizard_views.xml diff --git a/yuthon_property/views/property_accounts_receivable_views.xml b/yuthon_property/views/property_accounts_receivable_views.xml index 91ce9cf4e..1d06cab4a 100644 --- a/yuthon_property/views/property_accounts_receivable_views.xml +++ b/yuthon_property/views/property_accounts_receivable_views.xml @@ -37,6 +37,45 @@ + + + property.accounts.receivable.tree + property.accounts.receivable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 导入应收初始化数据 diff --git a/yuthon_property_approval/__manifest__.py b/yuthon_property_approval/__manifest__.py index 520e6db7e..dcbe38ed0 100644 --- a/yuthon_property_approval/__manifest__.py +++ b/yuthon_property_approval/__manifest__.py @@ -97,6 +97,7 @@ 'wizard/action_refuse_wizard_views.xml', 'wizard/property_bills_wizard_views.xml', 'wizard/property_meeting_wizard_views.xml', + 'wizard/property_deposit_wizard_views.xml', 'views/property_menu_all.xml', ], diff --git a/yuthon_property_approval/models/inherit_yuthon_property.py b/yuthon_property_approval/models/inherit_yuthon_property.py index 3e4244fde..4c34d1141 100644 --- a/yuthon_property_approval/models/inherit_yuthon_property.py +++ b/yuthon_property_approval/models/inherit_yuthon_property.py @@ -438,70 +438,70 @@ class PropertyBankStatements(models.Model): current_sequence += 10 + + all_vals = [] + service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1) + rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1) + deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100) + management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200) + rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210) + lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220) + guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230) + water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300) + electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400) + fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500) + + all_vals.extend(rent_vals) + all_vals.extend(deposit_vals) + all_vals.extend(lightbox_vals) + all_vals.extend(guanzi_vals) + all_vals.extend(service_vals) + all_vals.extend(management_vals) + all_vals.extend(water_vals) + all_vals.extend(electricity_vals) + all_vals.extend(fee_vals) + all_vals.extend(rename_vals) + + return all_vals, total_rent_month, + return [], 0 + + def _get_context_and_action(self, record, all_vals, total_rent_month): + """ + :param record: 银行收款记录 + :param all_vals: 应收费用列表 + :param total_rent_month: 应收总金额 + :return: + """ + start_time = self.start_time + relativedelta(hours=8) + warning_msg = self._check_unreconciled_statements(start_time) + context = { + "default_actual_date": fields.Date.to_date(start_time), + "default_bank_id": self.id, + "default_bank_branch": self.bank_branch, + "default_account": record.name, + "default_contract_ids": [(6, 0, self.contract_ids.ids)], + "default_actual_total": record.original_amount, + "default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金 + 'default_amount_reconciled': self.no_amount, + "default_receivable_total": total_rent_month, + "default_warning_msg":warning_msg, + "dialog_size": "extra-large", + "default_order": "due_date desc", + } + return { + "type": "ir.actions.act_window", + "name": "对账弹窗", + "res_model": "property.reconciliation.wizard", + "view_mode": "form", + "target": "new", + "dialog_size": "extra-large", + "context": context, + } if vals_list and type_name: used_type_names.add(type_name) return vals_list, current_sequence - all_vals = [] - service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1) - rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1) - deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100) - management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200) - rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210) - lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220) - guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230) - water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300) - electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400) - fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500) - - all_vals.extend(rent_vals) - all_vals.extend(deposit_vals) - all_vals.extend(lightbox_vals) - all_vals.extend(guanzi_vals) - all_vals.extend(service_vals) - all_vals.extend(management_vals) - all_vals.extend(water_vals) - all_vals.extend(electricity_vals) - all_vals.extend(fee_vals) - all_vals.extend(rename_vals) - - return all_vals, total_rent_month, - return [], 0 - - def _get_context_and_action(self, record, all_vals, total_rent_month): - """ - :param record: 银行收款记录 - :param all_vals: 应收费用列表 - :param total_rent_month: 应收总金额 - :return: - """ - start_time = self.start_time + relativedelta(hours=8) - warning_msg = self._check_unreconciled_statements(start_time) - context = { - "default_actual_date": fields.Date.to_date(start_time), - "default_bank_id": self.id, - "default_bank_branch": self.bank_branch, - "default_account": record.name, - "default_contract_ids": [(6, 0, self.contract_ids.ids)], - "default_actual_total": record.original_amount, - "default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金 - 'default_amount_reconciled': self.no_amount, - "default_receivable_total": total_rent_month, - "default_warning_msg":warning_msg, - "dialog_size": "extra-large", - "default_order": "due_date desc", - } - return { - "type": "ir.actions.act_window", - "name": "对账弹窗", - "res_model": "property.reconciliation.wizard", - "view_mode": "form", - "target": "new", - "dialog_size": "extra-large", - "context": context, - } - def _check_unreconciled_statements(self, current_transaction_time): """ 检查所有承租方对应的银行账号在property.bank.statements中是否有未对账流水 @@ -617,6 +617,175 @@ class PropertyAccountsReceivable(models.Model): actual_coping = fields.Float(string="实际应付", compute='_compute_actual_coping', store=True) charging_range = fields.Text(string="收费区间") + + def _get_reconciliation_vals(self, record, contract_id): + collection_type_map = { + 'rent': '租金', + 'deposit': '押金', + 'management_fee': '管理费', + 'lightbox': '灯箱', + 'guanzijian': '管子间', + 'water_fee': '水费', + 'electricity_fee': '电费', + 'rename_fee': '更名费', + 'service': '有偿服务收入', + 'fee': '人工费/日光灯款/代垫用料费', + } + used_type_names = set() + total_rent_month = 0 + base_domain = [ + ('contract_id', '=', contract_id.id), + ('is_invalid', '=', False), + ('is_reconciliation', '=', False), + ] + + def _search_receivable(management_type_name): + domain = base_domain + [('management_type_id.name', '=', management_type_name)] + return self.env['property.accounts.receivable'].search(domain) + + service_ids = _search_receivable('有偿服务收入') + rent_ids = _search_receivable('租金') + rename_fee_ids = _search_receivable('更名费') + deposit_ids = _search_receivable('押金') + management_ids = _search_receivable('管理费') + lightbox_ids = _search_receivable('灯箱') + guanzi_ids = _search_receivable('管子间') + water_ids = _search_receivable('水费') + electricity_ids = _search_receivable('电费') + fee_ids = _search_receivable('人工费/日光灯款/代垫用料费') + + def create_reconciliation_vals(ids, collection_type_val, sequence_val): + """ + :param ids:费用记录集 + :param collection_type_val: 费用类型(如 'rent', 'deposit') + :return: 格式化后的费用行列表 + """ + nonlocal total_rent_month + vals_list = [] + current_sequence = sequence_val + type_name = collection_type_map.get(collection_type_val, '') + # 特殊处理:押金类型需要去重相同合同的记录 + if collection_type_val == 'deposit': + # 使用字典来跟踪已处理的合同ID + processed_contracts = set() + for sub_record in ids: + contract_id = sub_record.contract_id.id if sub_record.contract_id else False + original_amount = sub_record.original_amount if sub_record.original_amount else 0 + submitted = sub_record.submitted if sub_record.submitted else 0 + # 跳过已处理的合同 + if contract_id and contract_id in processed_contracts: + continue + # 获取合同的实际结束日期作为到期日 + due_date = False + if contract_id: + due_date = sub_record.contract_id.actual_start_time + # 构建对账行数据 + vals = { + 'contract_id': contract_id, + 'rent_month': original_amount, + 'rent': submitted, + 'collection_type': collection_type_val, + 'sequence': current_sequence, + 'due_date': due_date, # 押金类型使用合同实际开始日期 + } + total_rent_month += original_amount # 累加总金额 + vals_list.append((0, 0, vals)) + # 标记此合同已处理 + if contract_id: + processed_contracts.add(contract_id) + + current_sequence += 10 + else: + # 非押金类型:正常处理每条记录 + for sub_record in ids: + contract_id = sub_record.contract_id.id if sub_record.contract_id else False + original_amount = sub_record.original_amount if sub_record.original_amount else 0 + submitted = sub_record.submitted if sub_record.submitted else 0 + due_date = sub_record.due_date if sub_record.due_date else False + vals = { + 'contract_id': contract_id, + 'rent_month': original_amount, + 'rent': submitted, + 'collection_type': collection_type_val, + 'sequence': current_sequence, + 'due_date': due_date, + } + total_rent_month += original_amount # 累加总金额 + vals_list.append((0, 0, vals)) + + current_sequence += 10 + + + all_vals = [] + service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1) + rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1) + deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100) + management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200) + rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210) + lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220) + guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230) + water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300) + electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400) + fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500) + + all_vals.extend(rent_vals) + all_vals.extend(deposit_vals) + all_vals.extend(lightbox_vals) + all_vals.extend(guanzi_vals) + all_vals.extend(service_vals) + all_vals.extend(management_vals) + all_vals.extend(water_vals) + all_vals.extend(electricity_vals) + all_vals.extend(fee_vals) + all_vals.extend(rename_vals) + + return all_vals, total_rent_month, + return [], 0 + + def _get_context_and_action(self, all_vals, total_rent_month): + """ + :param record: 银行收款记录 + :param all_vals: 应收费用列表 + :param total_rent_month: 应收总金额 + :return: + """ + start_time = self.start_time + relativedelta(hours=8) + # context = { + # "default_actual_date": fields.Date.to_date(start_time), + # "default_contract_ids": [(6, 0, self.contract_id.id)], + # "default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金 + # "default_receivable_total": total_rent_month, + # "dialog_size": "extra-large", + # "default_order": "due_date desc", + # } + return { + "type": "ir.actions.act_window", + "name": "押金对账弹窗", + "res_model": "property.deposit.wizard", + "view_mode": "form", + "target": "new", + "dialog_size": "extra-large", + # "context": context, + } + if vals_list and type_name: + used_type_names.add(type_name) + + return vals_list, current_sequence + + def verification_wizard(self): + for record in self: + # all_vals, total_rent_month = self._get_reconciliation_vals(record, record.contract_id) + return { + "type": "ir.actions.act_window", + "name": "押金对账弹窗", + "res_model": "property.deposit.wizard", + "view_mode": "form", + "target": "new", + "dialog_size": "extra-large", + # "context": context, + } + + @api.depends('contract_id', 'due_date') def compute_tenure_type(self): for record in self: diff --git a/yuthon_property_approval/security/ir.model.access.csv b/yuthon_property_approval/security/ir.model.access.csv index 95063af1f..45f4d056f 100644 --- a/yuthon_property_approval/security/ir.model.access.csv +++ b/yuthon_property_approval/security/ir.model.access.csv @@ -122,4 +122,5 @@ access_property_payment_notice,property_payment_notice,model_property_payment_no 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_action_refuse_wizard,action_refuse_wizard,model_action_refuse_wizard,base.group_user,1,1,1,1 \ No newline at end of file +access_action_refuse_wizard,action_refuse_wizard,model_action_refuse_wizard,base.group_user,1,1,1,1 +access_property_deposit_wizard,property_deposit_wizard,model_property_deposit_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/yuthon_property_approval/views/inherit_yuthon_property_views.xml b/yuthon_property_approval/views/inherit_yuthon_property_views.xml index 60af20fb6..3e647a83a 100644 --- a/yuthon_property_approval/views/inherit_yuthon_property_views.xml +++ b/yuthon_property_approval/views/inherit_yuthon_property_views.xml @@ -408,6 +408,43 @@ + + + + + + + + + + + property.accounts.receivable.tree + property.accounts.receivable + + + + + + + + + + + + + + + + + + + + + + + + + @@ -467,6 +504,15 @@ + + 押金列表 + property.accounts.receivable + tree + {'search_default_group_by_contract_id': True} + [('management_type_id.name','=', '押金')] + + + property.accounts.receivable.tree.form diff --git a/yuthon_property_approval/wizard/__init__.py b/yuthon_property_approval/wizard/__init__.py index c2e72931d..8766209c3 100644 --- a/yuthon_property_approval/wizard/__init__.py +++ b/yuthon_property_approval/wizard/__init__.py @@ -14,4 +14,5 @@ from . import build_pop_wizard from . import penalty_line_wizard from . import print_payment_wizard from . import property_bills_wizard -from . import action_refuse_wizard \ No newline at end of file +from . import action_refuse_wizard +from . import property_deposit_wizard \ No newline at end of file diff --git a/yuthon_property_approval/wizard/property_deposit_wizard.py b/yuthon_property_approval/wizard/property_deposit_wizard.py new file mode 100644 index 000000000..56103a19f --- /dev/null +++ b/yuthon_property_approval/wizard/property_deposit_wizard.py @@ -0,0 +1,648 @@ +from odoo import models, fields, api, _, SUPERUSER_ID +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError, ValidationError +import re + +import logging +_logger = logging.getLogger(__name__) + +class PropertyDepositWizard(models.Model): + _name = 'property.deposit.wizard' + _description = '押金对账弹窗' + + receivable_total = fields.Float(string='应收金额', compute="_compute_receivable_total", store=True) + bank_branch = fields.Char('银行名称') + warning_msg = fields.Char(string='警告') + bank_id = fields.Many2one('property.bank.statements', string='银行流水') + lessee_ids = fields.Many2many('property.tenant.information', string='承租方') + name_account = fields.Char( string='对方户名') + account = fields.Char(string='账号') + contract_ids = fields.Many2many('property.lease.contract', string='合同') + amount_been_reconciled = fields.Float(string='已对金额', store=True) + amount_water_been = fields.Float(string='已对流水金额', store=True) + amount_water = fields.Float(string='未对流水金额', store=True) + amount_reconciled = fields.Float(string='未核销', compute='_compute_receivable_total') + actual_total = fields.Float(string='实收金额') + actual_date = fields.Date(string='实收日期') + initial_total = fields.Float(string='初始金额', default=0.0) + rent = fields.Float(string='未收总租金', compute="_compute_rent_total") + penalty = fields.Float(string='未收总违约金', compute="_compute_penalty_total") + remake = fields.Text(string='摘要') + reconciliation_line_ids = fields.One2many('deposit.reconciliation.line', 'reconciliation_id', string='对账明细') + + # @api.onchange('bank_id') + # def _onchange_bank_id(self): + # for rec in self: + # rec.lessee_ids = rec.bank_id.lessee_ids.ids + # rec.name_account = rec.bank_id.name_account + # rec.remake = rec.bank_id.remake + # rec.amount_water_been = rec.bank_id.yes_amount + # rec.amount_water = rec.bank_id.no_amount + # + # @api.depends('reconciliation_line_ids') + # def _compute_receivable_total(self): + # for rec in self: + # rec.receivable_total = sum(line.rent for line in rec.reconciliation_line_ids) + # rec.amount_reconciled = max(0 , rec.bank_id.no_amount - sum(line.amount for line in self.reconciliation_line_ids)) + # + # @api.depends('reconciliation_line_ids.collection_type', 'reconciliation_line_ids.rent') + # def _compute_rent_total(self): + # """计算所有类型为租金的明细行的未收总额""" + # for wizard in self: + # # 筛选出收款类型为租金的明细行,并累加其未收金额 + # rent_total = sum( + # line.rent or 0.0 + # for line in wizard.reconciliation_line_ids + # if line.collection_type == 'rent' + # ) + # wizard.rent = rent_total + # + # @api.depends('reconciliation_line_ids.collection_type', 'reconciliation_line_ids.rent') + # def _compute_penalty_total(self): + # """计算所有类型为违约金的明细行的未收总额""" + # for wizard in self: + # penalty_total = sum( + # line.rent or 0.0 + # for line in wizard.reconciliation_line_ids + # if line.collection_type == 'penalty' + # ) + # wizard.penalty = penalty_total + # + # @api.onchange('receivables_type_ids') + # def onchange_receivables_type_ids(self): + # for rec in self: + # selected_type_names = rec.receivables_type_ids.mapped('name') + # if not selected_type_names: + # rec.reconciliation_line_ids = [(5, 0, 0)] + # continue + # # 收款类型与名称的映射(包含违约金,但后续会排除) + # type_name_map = { + # 'deposit': '押金', + # 'rent': '租金', + # 'water_fee': '水费', + # 'lightbox': '灯箱', + # 'guanzijian': '管子间', + # 'electricity_fee': '电费', + # 'penalty': '违约金', + # 'management_fee': '管理费', + # 'repair_fee': '维修费', + # 'fee': '人工费/日光灯款/代垫用料费', + # } + # allowed_by_selection = [ + # coll_type for coll_type, name in type_name_map.items() + # if name in selected_type_names + # ] + # to_delete = [] + # for line in rec.reconciliation_line_ids: + # if line.collection_type not in allowed_by_selection: + # to_delete.append((2, line.id, 0)) + # + # if to_delete: + # rec.reconciliation_line_ids = to_delete + + # @api.onchange('actual_total') + # def compute_reconciliation_line_ids(self): + # type_name_map = { + # 'deposit': '押金', + # 'rent': '租金', + # 'water_fee': '水费', + # 'lightbox': '灯箱', + # 'guanzijian': '管子间', + # 'electricity_fee': '电费', + # 'penalty': '违约金', + # 'management_fee': '管理费', + # 'repair_fee': '维修费', + # 'fee': '人工费/日光灯款/代垫用料费', + # } + # new_lines = [] + # # 违约金生成 + # for contract_id in self.contract_ids: + # contract_penalties = [] + # if not contract_id.lease_ids: + # continue + # receivable_penalties = self.env['property.accounts.receivable'].search([ + # ('contract_id', '=', contract_id.id.origin), + # ('is_reconciliation', '=', False), + # ('is_invalid', '=', False), + # ('management_type_id.name', '=', '违约金') + # ]) + # for penalty in receivable_penalties: + # contract_penalties.append({ + # 'contract_id': penalty.contract_id.id, + # 'rent_month': 0, + # 'due_date': penalty.due_date, + # 'rent': penalty.submitted, + # 'amount': 0, + # 'collection_type': 'penalty', + # 'remake': penalty.remark, + # }) + # contract_receivables_ids = self.env['property.accounts.receivable'].search([ + # ('contract_id', '=', contract_id.id.origin), + # ('is_reconciliation', '=', False), + # ('is_invalid', '=', False), + # ('management_type_id.name', 'not in', ['违约金', '押金']), + # ]) + # for contract_receivables in contract_receivables_ids: + # if contract_receivables: + # # 计算应收日期的下月1号 + # if not contract_receivables.due_date: + # raise UserError(f'合同{contract_id.name}类型为{contract_receivables.management_type_id.name}的应收日期为空,请先填写应收日期') + # next_month_first = (fields.Date.from_string(contract_receivables.due_date) + + # relativedelta(months=1)).replace(day=1) + # # 检查是否产生违约 + # if fields.Date.from_string(self.actual_date) >= next_month_first: + # # 计算违约天数和违约金 + # late_days = (fields.Date.from_string(self.actual_date) - next_month_first).days + 1 + # # 找到符合月份的租金 + # penalty_amount = late_days * round( + # contract_receivables.submitted * float(contract_id.percentage_liquidated_damages.name), 2 + # ) + # start_date_str = fields.Date.to_string(next_month_first) + # end_date_str = self.actual_date + # # 处理新产生的违约金 + # if penalty_amount > 0: + # contract_penalties.append({ + # 'contract_id': contract_id.id, + # 'rent_month': 0, + # 'due_date': self.actual_date, + # 'rent': penalty_amount, + # 'amount': 0, + # 'collection_type': 'penalty', + # 'remake': f'违约金({start_date_str}至{end_date_str},共{late_days}天)', + # }) + # + # # 查找应收是否有未交全的租金 + # unpaid_rent_recs = self.env['property.accounts.receivable'].search([ + # ('management_type_id.name', 'not in', ['违约金', '押金']), + # ('contract_id', '=', contract_id.id.origin), + # ('is_invalid', '=', False), + # ('submitted', '!=', 0), + # ('payout_date', '!=', None), + # ]) + # if unpaid_rent_recs: + # for rent in unpaid_rent_recs: + # if rent.payout_date <= rent.due_date: # 跳过预收的租金 + # continue + # first_day = (rent.payout_date + timedelta(days=1)) + # last_month_rent = rent.submitted + # penalty_days = self.actual_date - first_day + # penalty_amount = (penalty_days.days + 1) * round( + # last_month_rent * float(contract_id.percentage_liquidated_damages.name), 2 + # ) if contract_id else 0.0 + # if penalty_amount > 0: + # new_lines.append((0, 0, { + # 'contract_id': contract_id.id if contract_id else None, + # 'rent_month': 0, + # 'due_date': fields.Date.to_string(first_day), + # 'rent': penalty_amount, + # 'amount': 0, + # 'collection_type': 'penalty', + # 'remake': f'违约金({fields.Date.to_string(first_day)}至{fields.Date.to_string(self.actual_date)},共{penalty_days.days + 1}天)', + # })) + # + # # 3. 添加到总列表 + # for vals in contract_penalties: + # new_lines.append((0, 0, vals)) + # + # if new_lines: + # keep_lines = [ + # (4, line.id) for line in self.reconciliation_line_ids + # if line.collection_type != 'penalty' + # ] + # self.reconciliation_line_ids = keep_lines + new_lines + # + # all_penalties = all(line.collection_type == 'penalty' for line in self.reconciliation_line_ids) + # if all_penalties and self.reconciliation_line_ids: + # total = sum(line.rent for line in self.reconciliation_line_ids) + # self.receivable_total = total + # + # # all_due_dates = [] + # # for line in self.reconciliation_line_ids: + # # if line.collection_type == 'rent' and line.due_date: + # # all_due_dates.append(fields.Date.from_string(line.due_date)) + # # # 2. 找到最早的到期日期 + # # if all_due_dates: + # # earliest_due_date = min(all_due_dates) + # # # 获取实际日期(假设actual_date是日期对象,如果是字符串需要先转换) + # # actual_date = fields.Date.from_string(self.actual_date) if isinstance(self.actual_date, + # # str) else self.actual_date + # # + # # # 计算起始月份(最早到期日的月份)和结束月份(实际日期的月份) + # # start_month = earliest_due_date + relativedelta(months=1) + # # end_month = actual_date - relativedelta(months=1) + # # + # # temp_rent_set = set() + # # target_rent_months = [] + # # + # # # 遍历从起始月到结束月的所有月份 + # # current_month = start_month + # # while current_month <= end_month: + # # due_month = current_month.strftime('%Y-%m') + # # # 为每个租金行添加当前月份的记录(去重) + # # for line in self.reconciliation_line_ids: + # # if line.collection_type == 'rent': + # # unique_key = ( + # # line.contract_id.id if line.contract_id else None, + # # due_month + # # ) + # # if unique_key not in temp_rent_set: + # # temp_rent_set.add(unique_key) + # # target_rent_months.append({ + # # 'contract_id': line.contract_id, + # # 'due_month': due_month + # # }) + # # # 移动到下个月 + # # if current_month.month == 12: + # # current_month = current_month.replace(year=current_month.year + 1, month=1, day=1) + # # else: + # # current_month = current_month.replace(month=current_month.month + 1, day=1) + # # else: + # # 没有符合条件的明细行时,返回空列表 + # target_rent_months = [] + # + # # if target_rent_months: + # # existing_penalty_months = set() + # # for line in self.reconciliation_line_ids: + # # if line.collection_type == 'penalty' and line.due_date: + # # penalty_month = fields.Date.from_string(line.due_date).strftime('%Y-%m') + # # penalty_key = ( + # # line.contract_id.id if line.contract_id else None, + # # penalty_month + # # ) + # # existing_penalty_months.add(penalty_key) + # # + # # # # 3. 补全缺失的跨月违约金(按合同+年月匹配,缺失则新增) + # # # missing_penalty_lines = [] + # # # for rent_info in target_rent_months: + # # # contract = rent_info['contract_id'] + # # # due_month = rent_info['due_month'] + # # # + # # # # 构建待检查的违约金key(合同+年月) + # # # check_key = ( + # # # contract.id if contract else None, + # # # due_month + # # # ) + # # # + # # # if check_key not in existing_penalty_months: + # # # year, month = map(int, due_month.split('-')) + # # # first_day = datetime(year, month, 1).date() + # # # penalty_days = (self.actual_date - first_day).days + 1 + # # # + # # # # 计算补全违约金的日期范围:当月1号 ~ 当月最后1天 + # # # # 查找上个月的租金明细行 + # # # if month == 1: + # # # last_month_year = year - 1 + # # # last_month = 12 + # # # else: + # # # last_month_year = year + # # # last_month = month - 1 + # # # last_month_str = f"{last_month_year}-{last_month:02d}" + # # # + # # # last_month_rent_line = next(( + # # # line for line in self.reconciliation_line_ids + # # # if line.collection_type == 'rent' + # # # and line.contract_id == contract + # # # and line.due_date # 有到期日期 + # # # and fields.Date.from_string(line.due_date).strftime('%Y-%m') == last_month_str + # # # ), None) + # # # # 提取上个月租金的rent金额 + # # # last_month_rent = last_month_rent_line.rent if last_month_rent_line else 0.0 + # # # penalty_amount = penalty_days * round( + # # # last_month_rent * float(contract.percentage_liquidated_damages.name), 2 + # # # ) if (contract and last_month_rent > 0) else 0.0 + # # # + # # # if penalty_amount > 0: + # # # missing_penalty_lines.append((0, 0, { + # # # 'contract_id': contract.id if contract else None, + # # # 'rent_month': 0, + # # # 'due_date': fields.Date.to_string(first_day), + # # # 'rent': penalty_amount, + # # # 'amount': 0, + # # # 'collection_type': 'penalty', + # # # 'remake': f'违约金({fields.Date.to_string(first_day)}至{fields.Date.to_string(self.actual_date)},共{penalty_days}天', + # # # })) + # # # + # # # # 添加缺失的违约金记录 + # # # if missing_penalty_lines: + # # # self.write({'reconciliation_line_ids': missing_penalty_lines}) + # + # self.create_reconciliation_vals() + # line_types = [] + # for line in self.reconciliation_line_ids: + # if line.collection_type not in line_types: + # line_types.append(line.collection_type) + # type_names = [type_name_map.get(t) for t in line_types if type_name_map.get(t)] + # receivable_types = self.env['yuthon.management.type'].search([ + # ('name', 'in', type_names) + # ]) + + def create_reconciliation_vals(self): + """ + 按已生成的明细行 分配金额(优先处理押金) + :return: + """ + remaining_amount = self.bank_id.no_amount + penalty_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "penalty"] # 违约金行 + rent_lines = [line for line in self.reconciliation_line_ids if line.contract_id.contract_code == "(租)(管)TX2023-072"] # 租金行 + + type_priority = { + 'deposit': 1, # 押金优先 + 'rent': 2, # 其次租金 + 'other': 3, # 其他类型 + 'penalty': 4 + } + all_lines = sorted( + self.reconciliation_line_ids, + key=lambda line: ( + line.due_date.strftime('%Y-%m') if line.due_date else '', + type_priority.get(line.collection_type, 3) + ) + ) + for line in all_lines: + if remaining_amount <= 0: + break + if line.collection_type == "penalty": + continue + # 分配当前行金额 + line.amount = 0 + need_amount = round(line.rent or 0, 2) + assign_amount = min(need_amount, remaining_amount) + line.amount = assign_amount + remaining_amount -= assign_amount + if assign_amount > 0 and round(line.amount or 0, 2)>0: + line.onchange_remake() + + if remaining_amount > 0: + penalty_lines = [line for line in all_lines if line.collection_type == "penalty"] + for line in penalty_lines: + if remaining_amount <= 0: + break + line.amount = 0 + need_amount = line.rent or 0 + assign_amount = min(need_amount, remaining_amount) + line.amount = assign_amount + remaining_amount -= assign_amount + if assign_amount > 0 and round(line.amount or 0, 2) > 0: + line.onchange_remake() + + for line in penalty_lines: + line.color = 'blue' + + for line in rent_lines: + line.color = 'success' + + + def confirm(self): + collection_type_mapping = { + 'deposit': '押金', + 'rent': '租金', + 'lightbox': '灯箱', + 'guanzijian': '管子间', + 'water_fee': '水费', + 'electricity_fee': '电费', + 'penalty': '违约金', + 'management_fee': '管理费', + 'repair_fee': '维修费', + 'other': '其他' + } + for rec in self: + bank_lines = self.env['property.bank.line'].search([ + ('bank', '=', rec.bank_id.name), + ('tenant_id.name', '!=', False) + ]) + if not bank_lines: + information = self.env['property.tenant.information'].search( + [('name', '=', rec.bank_id.name_account)], + limit=1) + if information: + bank_lines = self.env['property.bank.line'].create({ + 'tenant_id': information.id, + 'bank': rec.bank_id.name, + 'user': information.name, + 'name': rec.bank_id.bank_branch, + }) + remaining_amount = 0 + for line in rec.reconciliation_line_ids: + remaining_amount += line.amount + + if round(rec.amount_water, 2) > round(remaining_amount, 2): + rec.bank_id.registered = rec.amount_water - remaining_amount + rec.bank_id.bank_state = 'registered' + else: + rec.bank_id.registered = 0 + rec.bank_id.bank_state = 'reconciled' + rec.bank_id.reconciliation_time = fields.Datetime.now() - timedelta(hours=8) + yes_amount = 0 + for line in rec.reconciliation_line_ids: + # 跳过金额为0的行 + if (round(line.amount or 0, 2) == 0 and line.collection_type != 'penalty') or \ + (line.contract_id and line.contract_id.is_contract): + continue + first_day = line.due_date.replace(day=1) + last_day = (first_day + relativedelta(months=1)) - relativedelta(days=1) + if line.due_date >= self.actual_date and line.collection_type == 'deposit': + domain = [ + ('management_type_id.name', '=', collection_type_mapping.get(line.collection_type)), + ('is_reconciliation', '=', False), + ('is_suspend', '=', False), + ] + else: + domain = [ + ('management_type_id.name', '=', collection_type_mapping.get(line.collection_type)), + ('is_reconciliation', '=', False), + ('is_suspend', '=', False), + ('due_date', '>=', first_day), + ('due_date', '<=', last_day) + ] + if line.contract_id: + domain.append(('contract_id', '=', line.contract_id.id)) + receivable_ids = self.env['property.accounts.receivable'].search(domain) + for receivable in receivable_ids: + if round(line.rent or 0, 2) == round(line.amount or 0, 2): + is_reconciliation = True + else: + is_reconciliation = False + if receivable and receivable.remark: + final_remark = f"{receivable.remark}\n{line.remake}" + else: + final_remark = line.remake + if line.collection_type != 'penalty': + receivable.delivered1 = receivable.delivered1 + line.amount + receivable.write({ + 'is_reconciliation': is_reconciliation, + 'payout_date': rec.actual_date, + 'remark': final_remark, + 'reconciliation_time': fields.Datetime.now(), + 'receivable_log': f'当前登录人{rec.env.user.name}于{fields.Datetime.now()}对户名为{rec.bank_id.name_account}进行对账操作,对账应收金额为{rec.receivable_total},实际金额为{rec.amount_water}' + }) + receivable.contract_id.update_contact_state() + self.env['receivable.modify.line'].create({ + 'bank_id': rec.bank_id.id, + 'not_collected': line.rent, + 'reconciliation_date': rec.actual_date if line.amount != 0 else None, + 'receivable_id': receivable.id, + 'is_write': True if line.amount != 0 else False, + 'is_penalty': False, + 'amount': line.amount, + }) + + management_type_id = self.env['yuthon.management.type'].search([('name', '=', '违约金')]) + if line.collection_type == 'penalty': + penalty_date = line.due_date + first_day_of_last_month = penalty_date.replace(day=1) - relativedelta(months=2) + # 上个月最后一天:当前月第一天减1天 + last_day_of_last_month = penalty_date.replace(day=1) - relativedelta(days=1) + is_create = self.env['deposit.reconciliation.line'].search([ + ('reconciliation_id', '=', self.id), + ('contract_id', '=', line.contract_id.id ), + ('due_date', '>=', first_day_of_last_month), + ('due_date', '<=', last_day_of_last_month), + ('collection_type', '!=', 'penalty'), + ('amount', '>', 0) + ]) + # 用备注的的开始日期和结束日期 + date_pattern = r'(\d{4}-\d{2}-\d{2})[至-](\d{4}-\d{2}-\d{2})' + date_match = re.search(date_pattern, line.remake or '') + extract_start_date = date_match.group(1) if date_match else line.due_date + extract_end_date = date_match.group(2) if date_match else None + check_domain = [ + ('due_date', '=', line.due_date), + ('contract_id', '=', line.contract_id.id), + ('submitted', '=', line.rent), + ('is_invalid', '=', False), + ('management_type_id', '=', management_type_id.id), + ] + # 检查是否已有相同记录 + existing_record = self.env['property.accounts.receivable'].search(check_domain) + is_reconciliation = round(line.amount,2) == round(line.rent, 2) + receivable_id = self.env['property.accounts.receivable'] + if not existing_record and is_create: + domain2 = [ + ('due_date', '=', line.due_date), + ('contract_id', '=', line.contract_id.id), + ('collection_type', '=', 'rent'), + ] + extract_start_date = datetime.strptime(extract_start_date, "%Y-%m-%d").date() + extract_end_date = datetime.strptime(extract_end_date, "%Y-%m-%d").date() + reconciliation_id = self.env['deposit.reconciliation.line'].search(domain2, limit=1) + receivable_id = self.env['property.accounts.receivable'].create({ + 'is_reconciliation': is_reconciliation, + 'due_date': extract_end_date, + 'due_date_end': None, + 'penalty': reconciliation_id.rent, + 'management_type_id': management_type_id.id, + 'contract_id': line.contract_id.id, + 'delivered1': line.amount, + 'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None, + 'original_amount': line.rent, + 'remark': line.remake, + 'reconciliation_time': fields.Datetime.now(), + 'charging_range': f"{extract_start_date} 到 {extract_end_date}共{(extract_end_date - extract_start_date).days + 1}" + }) + else: + if line.amount: + existing_record.write({ + 'delivered1': existing_record.delivered1 + line.amount, + 'due_date': extract_end_date, + 'due_date_end': None, + 'is_reconciliation': is_reconciliation, + 'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None, + 'remark': line.remake, + 'bank_id': rec.bank_id.id if line.amount != 0 else None, + 'reconciliation_time': fields.Datetime.now(), + }) + + if existing_record: + receivable_id = existing_record + + if receivable_id: + self.env['receivable.modify.line'].create({ + 'bank_id': rec.bank_id.id, + 'not_collected': line.rent, + 'is_write': True if line.amount != 0 else False, + 'is_penalty': True, + 'reconciliation_date': rec.actual_date if line.amount != 0 else None, + 'receivable_id': receivable_id.id, + 'amount': line.amount, + }) + + rec.amount_been_reconciled += line.amount + yes_amount += line.amount + + rec.bank_id.yes_amount = rec.bank_id.yes_amount + yes_amount + rec.bank_id.change_amount = sum(line.amount for line in rec.reconciliation_line_ids) + + all_contracts = self.contract_ids + remaining_contract_ids = [] + for contract in all_contracts: + unreconciled_receivables = self.env["property.accounts.receivable"].search([ + ('contract_id', '=', contract.id), + ('is_reconciliation', '=', False), + ('is_invalid', '=', False), + ], limit=1) + if unreconciled_receivables: + remaining_contract_ids.append(contract.id) + + +class DepositReconciliationLine(models.Model): + _name = 'deposit.reconciliation.line' + _description = '押金对账明细' + _order = 'due_date desc' + + reconciliation_id = fields.Many2one('property.deposit.wizard', string='关联押金对账单弹窗') + sequence = fields.Integer(string='顺序') + property_name = fields.Text(string='物业', compute="_compute_property_name") + property_ids = fields.Many2many('yuthon.property', string='物业', compute="_compute_property_ids", store=True) + contract_id = fields.Many2one('property.lease.contract', string='合同编号') + rent_month = fields.Float(string='当期金额') + due_date = fields.Date(string='应收日期') + rent = fields.Float(string='未收') + amount = fields.Float(string='金额', store=True) + collection_type = fields.Selection([('deposit', '押金'), ('rent', '租金'), ('water_fee', '水费'), ('lightbox', '灯箱'), ('guanzijian', '管子间'), + ('electricity_fee', '电费'), ('penalty', '违约金'), ('management_fee', '管理费'), ('repair_fee', '维修费'), ('fee', '人工费/日光灯款/代垫用料费'), ('rename_fee', '更名费'), ('service', '有偿服务收入')], string='收款类型', store=True) + remake = fields.Text(string='备注') + color = fields.Selection([('success', '绿'), ('blue', '蓝')], string='颜色') + + @api.depends('contract_id') + def _compute_contract_name(self): + for record in self: + if record.contract_id and record.contract_id.version: + record.contract_name = record.contract_id.contract_code + record.contract_id.version + + @api.depends('contract_id') + def _compute_property_name(self): + for record in self: + record.property_name = record.contract_id.property_name + + @api.depends('contract_id', 'contract_id.lease_ids', 'contract_id.lease_ids.address_id') + def _compute_property_ids(self): + for record in self: + property_ids = self.env['yuthon.property'] + if record.contract_id and record.contract_id.lease_ids: + property_ids = record.contract_id.lease_ids.mapped('address_id') + record.property_ids = property_ids + + @api.onchange('amount', 'due_date', 'rent', 'collection_type', 'reconciliation_id.actual_date') + def onchange_remake(self): + for line in self: + actual_date = fields.Date.to_date(line.reconciliation_id.bank_id.start_time + relativedelta(hours=8)) + if round(line.amount or 0, 2) == 0 and line.collection_type != 'penalty': + line.remake = "" + return + if line.amount and line.collection_type != 'penalty': + due_date = line.due_date + actual_year, actual_month = actual_date.year, actual_date.month + due_year, due_month = due_date.year, due_date.month + due_date_str = due_date.strftime('%Y-%m') + actual_date_str = actual_date.strftime('%Y-%m-%d') + rent = round(line.rent or 0, 2) + if (due_month > actual_month and due_year == actual_year) or (due_year > actual_year): + if rent >= line.amount: + line.remake = f'{actual_date_str}预{due_date_str},欠{round(rent - line.amount, 2)}' + elif due_month == actual_month and due_year == actual_year: + if rent >= line.amount: + line.remake = f'交{due_date_str},欠{round(rent - line.amount, 2)}' + elif (due_month < actual_month and due_year == actual_year) or (due_year < actual_year): + if rent >= line.amount: + line.remake = f'{actual_date_str}补{due_date_str},欠{round(rent - line.amount, 2)}' \ No newline at end of file diff --git a/yuthon_property_approval/wizard/property_deposit_wizard_views.xml b/yuthon_property_approval/wizard/property_deposit_wizard_views.xml new file mode 100644 index 000000000..543dcc516 --- /dev/null +++ b/yuthon_property_approval/wizard/property_deposit_wizard_views.xml @@ -0,0 +1,65 @@ + + + + property.deposit.wizard.form + property.deposit.wizard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/yuthon_property_approval/wizard/property_reconciliation_wizard_views.xml b/yuthon_property_approval/wizard/property_reconciliation_wizard_views.xml index 5044d58d6..74495bf5e 100644 --- a/yuthon_property_approval/wizard/property_reconciliation_wizard_views.xml +++ b/yuthon_property_approval/wizard/property_reconciliation_wizard_views.xml @@ -12,7 +12,7 @@ - + @@ -22,7 +22,6 @@ - diff --git a/yuthon_salary/security/salary_security_rule.xml b/yuthon_salary/security/salary_security_rule.xml index 6060e35cd..ec308053c 100644 --- a/yuthon_salary/security/salary_security_rule.xml +++ b/yuthon_salary/security/salary_security_rule.xml @@ -1,5 +1,10 @@ + + 职级类型多公司 + + [('company_id', 'in', company_ids)] + 薪资方法多公司 diff --git a/yuthon_work_brand/views/hr_menus.xml b/yuthon_work_brand/views/hr_menus.xml index 839106392..37dc338d0 100644 --- a/yuthon_work_brand/views/hr_menus.xml +++ b/yuthon_work_brand/views/hr_menus.xml @@ -874,7 +874,7 @@ sequence="50"/> @@ -889,8 +889,14 @@ name="应付账单" action="yuthon_property_approval.view_property_bills_list_action" sequence="4"/> + From 8fb842829d07c3cd83954ea89b8d632d3f193bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Fri, 19 Dec 2025 21:57:15 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../property_accounts_receivable_views.xml | 1 - .../models/inherit_yuthon_property.py | 326 +++------ .../models/property_lease_renewal.py | 51 +- .../security/ir.model.access.csv | 3 +- .../views/inherit_yuthon_property_views.xml | 10 +- .../views/property_lease_renewal.xml | 77 +- .../wizard/property_deposit_wizard.py | 659 ++---------------- .../wizard/property_deposit_wizard_views.xml | 53 +- 8 files changed, 252 insertions(+), 928 deletions(-) diff --git a/yuthon_property/views/property_accounts_receivable_views.xml b/yuthon_property/views/property_accounts_receivable_views.xml index 1d06cab4a..9cda09ccb 100644 --- a/yuthon_property/views/property_accounts_receivable_views.xml +++ b/yuthon_property/views/property_accounts_receivable_views.xml @@ -72,7 +72,6 @@ - diff --git a/yuthon_property_approval/models/inherit_yuthon_property.py b/yuthon_property_approval/models/inherit_yuthon_property.py index 4c34d1141..ac31804ca 100644 --- a/yuthon_property_approval/models/inherit_yuthon_property.py +++ b/yuthon_property_approval/models/inherit_yuthon_property.py @@ -438,70 +438,70 @@ class PropertyBankStatements(models.Model): current_sequence += 10 - - all_vals = [] - service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1) - rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1) - deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100) - management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200) - rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210) - lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220) - guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230) - water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300) - electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400) - fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500) - - all_vals.extend(rent_vals) - all_vals.extend(deposit_vals) - all_vals.extend(lightbox_vals) - all_vals.extend(guanzi_vals) - all_vals.extend(service_vals) - all_vals.extend(management_vals) - all_vals.extend(water_vals) - all_vals.extend(electricity_vals) - all_vals.extend(fee_vals) - all_vals.extend(rename_vals) - - return all_vals, total_rent_month, - return [], 0 - - def _get_context_and_action(self, record, all_vals, total_rent_month): - """ - :param record: 银行收款记录 - :param all_vals: 应收费用列表 - :param total_rent_month: 应收总金额 - :return: - """ - start_time = self.start_time + relativedelta(hours=8) - warning_msg = self._check_unreconciled_statements(start_time) - context = { - "default_actual_date": fields.Date.to_date(start_time), - "default_bank_id": self.id, - "default_bank_branch": self.bank_branch, - "default_account": record.name, - "default_contract_ids": [(6, 0, self.contract_ids.ids)], - "default_actual_total": record.original_amount, - "default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金 - 'default_amount_reconciled': self.no_amount, - "default_receivable_total": total_rent_month, - "default_warning_msg":warning_msg, - "dialog_size": "extra-large", - "default_order": "due_date desc", - } - return { - "type": "ir.actions.act_window", - "name": "对账弹窗", - "res_model": "property.reconciliation.wizard", - "view_mode": "form", - "target": "new", - "dialog_size": "extra-large", - "context": context, - } if vals_list and type_name: used_type_names.add(type_name) return vals_list, current_sequence + all_vals = [] + service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1) + rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1) + deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100) + management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200) + rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210) + lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220) + guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230) + water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300) + electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400) + fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500) + + all_vals.extend(rent_vals) + all_vals.extend(deposit_vals) + all_vals.extend(lightbox_vals) + all_vals.extend(guanzi_vals) + all_vals.extend(service_vals) + all_vals.extend(management_vals) + all_vals.extend(water_vals) + all_vals.extend(electricity_vals) + all_vals.extend(fee_vals) + all_vals.extend(rename_vals) + + return all_vals, total_rent_month, + return [], 0 + + def _get_context_and_action(self, record, all_vals, total_rent_month): + """ + :param record: 银行收款记录 + :param all_vals: 应收费用列表 + :param total_rent_month: 应收总金额 + :return: + """ + start_time = self.start_time + relativedelta(hours=8) + warning_msg = self._check_unreconciled_statements(start_time) + context = { + "default_actual_date": fields.Date.to_date(start_time), + "default_bank_id": self.id, + "default_bank_branch": self.bank_branch, + "default_account": record.name, + "default_contract_ids": [(6, 0, self.contract_ids.ids)], + "default_actual_total": record.original_amount, + "default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金 + 'default_amount_reconciled': self.no_amount, + "default_receivable_total": total_rent_month, + "default_warning_msg":warning_msg, + "dialog_size": "extra-large", + "default_order": "due_date desc", + } + return { + "type": "ir.actions.act_window", + "name": "对账弹窗", + "res_model": "property.reconciliation.wizard", + "view_mode": "form", + "target": "new", + "dialog_size": "extra-large", + "context": context, + } + def _check_unreconciled_statements(self, current_transaction_time): """ 检查所有承租方对应的银行账号在property.bank.statements中是否有未对账流水 @@ -617,173 +617,51 @@ class PropertyAccountsReceivable(models.Model): actual_coping = fields.Float(string="实际应付", compute='_compute_actual_coping', store=True) charging_range = fields.Text(string="收费区间") - - def _get_reconciliation_vals(self, record, contract_id): - collection_type_map = { - 'rent': '租金', - 'deposit': '押金', - 'management_fee': '管理费', - 'lightbox': '灯箱', - 'guanzijian': '管子间', - 'water_fee': '水费', - 'electricity_fee': '电费', - 'rename_fee': '更名费', - 'service': '有偿服务收入', - 'fee': '人工费/日光灯款/代垫用料费', - } - used_type_names = set() - total_rent_month = 0 - base_domain = [ - ('contract_id', '=', contract_id.id), - ('is_invalid', '=', False), - ('is_reconciliation', '=', False), - ] - - def _search_receivable(management_type_name): - domain = base_domain + [('management_type_id.name', '=', management_type_name)] - return self.env['property.accounts.receivable'].search(domain) - - service_ids = _search_receivable('有偿服务收入') - rent_ids = _search_receivable('租金') - rename_fee_ids = _search_receivable('更名费') - deposit_ids = _search_receivable('押金') - management_ids = _search_receivable('管理费') - lightbox_ids = _search_receivable('灯箱') - guanzi_ids = _search_receivable('管子间') - water_ids = _search_receivable('水费') - electricity_ids = _search_receivable('电费') - fee_ids = _search_receivable('人工费/日光灯款/代垫用料费') - - def create_reconciliation_vals(ids, collection_type_val, sequence_val): - """ - :param ids:费用记录集 - :param collection_type_val: 费用类型(如 'rent', 'deposit') - :return: 格式化后的费用行列表 - """ - nonlocal total_rent_month - vals_list = [] - current_sequence = sequence_val - type_name = collection_type_map.get(collection_type_val, '') - # 特殊处理:押金类型需要去重相同合同的记录 - if collection_type_val == 'deposit': - # 使用字典来跟踪已处理的合同ID - processed_contracts = set() - for sub_record in ids: - contract_id = sub_record.contract_id.id if sub_record.contract_id else False - original_amount = sub_record.original_amount if sub_record.original_amount else 0 - submitted = sub_record.submitted if sub_record.submitted else 0 - # 跳过已处理的合同 - if contract_id and contract_id in processed_contracts: - continue - # 获取合同的实际结束日期作为到期日 - due_date = False - if contract_id: - due_date = sub_record.contract_id.actual_start_time - # 构建对账行数据 - vals = { - 'contract_id': contract_id, - 'rent_month': original_amount, - 'rent': submitted, - 'collection_type': collection_type_val, - 'sequence': current_sequence, - 'due_date': due_date, # 押金类型使用合同实际开始日期 - } - total_rent_month += original_amount # 累加总金额 - vals_list.append((0, 0, vals)) - # 标记此合同已处理 - if contract_id: - processed_contracts.add(contract_id) - - current_sequence += 10 - else: - # 非押金类型:正常处理每条记录 - for sub_record in ids: - contract_id = sub_record.contract_id.id if sub_record.contract_id else False - original_amount = sub_record.original_amount if sub_record.original_amount else 0 - submitted = sub_record.submitted if sub_record.submitted else 0 - due_date = sub_record.due_date if sub_record.due_date else False - vals = { - 'contract_id': contract_id, - 'rent_month': original_amount, - 'rent': submitted, - 'collection_type': collection_type_val, - 'sequence': current_sequence, - 'due_date': due_date, - } - total_rent_month += original_amount # 累加总金额 - vals_list.append((0, 0, vals)) - - current_sequence += 10 - - - all_vals = [] - service_vals, next_seq = create_reconciliation_vals(service_ids, 'service', 1) - rent_vals, next_seq = create_reconciliation_vals(rent_ids, 'rent', 1) - deposit_vals, next_seq = create_reconciliation_vals(deposit_ids, 'deposit', 100) - management_vals, next_seq = create_reconciliation_vals(management_ids, 'management_fee', 200) - rename_vals, next_seq = create_reconciliation_vals(rename_fee_ids, 'rename_fee', 210) - lightbox_vals, next_seq = create_reconciliation_vals(lightbox_ids, 'lightbox', 220) - guanzi_vals, next_seq = create_reconciliation_vals(guanzi_ids, 'guanzijian', 230) - water_vals, next_seq = create_reconciliation_vals(water_ids, 'water_fee', 300) - electricity_vals, next_seq = create_reconciliation_vals(electricity_ids, 'electricity_fee', 400) - fee_vals, next_seq = create_reconciliation_vals(fee_ids, 'fee', 500) - - all_vals.extend(rent_vals) - all_vals.extend(deposit_vals) - all_vals.extend(lightbox_vals) - all_vals.extend(guanzi_vals) - all_vals.extend(service_vals) - all_vals.extend(management_vals) - all_vals.extend(water_vals) - all_vals.extend(electricity_vals) - all_vals.extend(fee_vals) - all_vals.extend(rename_vals) - - return all_vals, total_rent_month, - return [], 0 - - def _get_context_and_action(self, all_vals, total_rent_month): - """ - :param record: 银行收款记录 - :param all_vals: 应收费用列表 - :param total_rent_month: 应收总金额 - :return: - """ - start_time = self.start_time + relativedelta(hours=8) - # context = { - # "default_actual_date": fields.Date.to_date(start_time), - # "default_contract_ids": [(6, 0, self.contract_id.id)], - # "default_reconciliation_line_ids": all_vals, # 租金,应收日期排序、管理费、电、水、押金 - # "default_receivable_total": total_rent_month, - # "dialog_size": "extra-large", - # "default_order": "due_date desc", - # } - return { - "type": "ir.actions.act_window", - "name": "押金对账弹窗", - "res_model": "property.deposit.wizard", - "view_mode": "form", - "target": "new", - "dialog_size": "extra-large", - # "context": context, - } - if vals_list and type_name: - used_type_names.add(type_name) - - return vals_list, current_sequence - def verification_wizard(self): - for record in self: - # all_vals, total_rent_month = self._get_reconciliation_vals(record, record.contract_id) - return { - "type": "ir.actions.act_window", - "name": "押金对账弹窗", - "res_model": "property.deposit.wizard", - "view_mode": "form", - "target": "new", - "dialog_size": "extra-large", - # "context": context, + receivable_lines = self.env['property.accounts.receivable'].search([ + ('contract_id', '=', self.contract_id.id), + ('is_reconciliation', '=', False), + ]) + receivable_line_vals = [] + for line in receivable_lines: + receivable_line_vals.append((0, 0, { + 'due_date': line.due_date, + 'management_type_id': line.management_type_id.id, + 'amount': line.submitted, + 'payment_type': 'pay', + 'receivable_id': line.id, + 'remake': line.remark, + })) + + bills_lines = self.env['property.bills.list'].search([ + ('contract_id', '=', self.contract_id.id), + ('actual_date', '!=', False), + ]) + bills_line_vals = [] + for line in bills_lines: + bills_line_vals.append((0, 0, { + 'due_date': line.due_date, + 'management_type_id': line.management_type_id.id, + 'amount': line.amount, + 'bills_id': line.id, + 'payment_type': 'spending', + 'remake': line.remake + })) + all_line_vals = receivable_line_vals + bills_line_vals + return { + "type": "ir.actions.act_window", + "name": "押金核销弹窗", + "res_model": "property.deposit.wizard", + "view_mode": "form", + "target": "new", + "context": { + "default_contract_id": self.contract_id.id, + "default_lessee_id": self.lessee_id.id, + "default_actual_date": self.due_date, + "default_deposit": self.original_amount, + 'default_reconciliation_line_ids': all_line_vals } + } @api.depends('contract_id', 'due_date') diff --git a/yuthon_property_approval/models/property_lease_renewal.py b/yuthon_property_approval/models/property_lease_renewal.py index 0146a3b78..614f47a5c 100644 --- a/yuthon_property_approval/models/property_lease_renewal.py +++ b/yuthon_property_approval/models/property_lease_renewal.py @@ -60,11 +60,22 @@ class PropertyLeaseRenewal(models.Model): deposit = fields.Float(string="原押金") is_full_refund = fields.Boolean(string="是否全额退押", default=True) refund_amount = fields.Float(string="退款金额") + refund_remake = fields.Char(string="备注") + total_receivables = fields.Float(string="应收金额", compute="_compute_total_receivables") + total_remake = fields.Char(string="备注") balance = fields.Float(string="余额") - remake = fields.Text(string="备注") + balance_remake = fields.Char(string="备注") receivables_ids = fields.One2many('early.receivables.line', 'renewal_id', string='应收明细') + @api.depends('receivables_ids', 'receivables_ids.original_amount', 'receivables_ids.reduction_amount', + 'receivables_ids.void') + def _compute_total_receivables(self): + for renewal in self: + valid_receivable_lines = renewal.receivables_ids.filtered(lambda line: not line.void and line.is_reconciliation == False) + total = sum(line.original_amount - line.reduction_amount for line in valid_receivable_lines) + renewal.total_receivables = total + @api.onchange('refund_amount') def _onchange_refund_amount(self): for rec in self: @@ -177,19 +188,30 @@ class PropertyLeaseRenewal(models.Model): 'contract_id': new_contract.id }) else: - receivable_ids = self.env['property.accounts.receivable'].search([ - ('due_date', '<=', rec.early_quitting_date), - ('contract_id', '=', rec.contract_id.id), - ]) + # receivable_ids = self.env['property.accounts.receivable'].search([ + # ('due_date', '<=', rec.early_quitting_date), + # ('contract_id', '=', rec.contract_id.id), + # ]) # if any(not receivable.is_reconciliation for receivable in receivable_ids): # raise UserError("该合同下存在未对账的应收单,无法提前退押金") rec.contract_id.write({ 'actual_end_time': rec.early_quitting_date }) - rec.contract_id.get_property_contract_time() + # rec.contract_id.get_property_contract_time() + if rec.balance > rec.total_receivables: + management_type_id = self.env['yuthon.management.type'].search([ + ('name', '=', '违约金') + ]) + self.env['property.accounts.receivable'].create({ + 'contract_id': rec.contract_id.id, + 'management_type_id': management_type_id.id, + 'due_date': rec.early_quitting_date, + 'original_amount': rec.balance - rec.total_receivables, + 'remark': rec.total_remake, + }) if rec.receivables_ids: - refund_lines = rec.receivables_ids.filtered(lambda l: l.refund_amount > 0) + refund_lines = rec.receivables_ids.filtered(lambda l: l.refund_amount > 0 and management_type_id.name != '押金') bills_line_list = [] # 除押金的明细 if refund_lines: @@ -199,7 +221,7 @@ class PropertyLeaseRenewal(models.Model): 'management_type_id': refund_line.management_type_id.id, 'amount': refund_line.refund_amount, 'contract_id': rec.contract_id.id, - 'remake': rec.remake + 'remake': rec.refund_remake }) # 押金 deposit_management_type = self.env['yuthon.management.type'].search( @@ -213,7 +235,7 @@ class PropertyLeaseRenewal(models.Model): 'management_type_id': deposit_management_type.id, 'amount': deposit_amount, 'contract_id': rec.contract_id.id, - 'remake': rec.remake + 'remake': rec.refund_remake }) if not rec.is_full_refund: @@ -222,7 +244,7 @@ class PropertyLeaseRenewal(models.Model): 'start_time': start_time, 'original_amount': rec.balance, 'name_account': rec.lessee_id.name, - 'remake': rec.remake, + 'remake': rec.refund_remake, }) # 创建应付账单 @@ -233,19 +255,16 @@ class PropertyLeaseRenewal(models.Model): for receivable_id in rec.receivables_ids: if receivable_id.void: receivable_id.receivable_id.write({ - 'is_void': True, + 'is_invalid': True, 'void_date': fields.Date.today() }) if receivable_id.reduction_amount: - receivable_id.receivable_id.reduction_amount = receivable_id.reduction_amount + receivable_id.receivable_id.discount_amount = receivable_id.reduction_amount receivable_id.receivable_id.discount_date = fields.Date.today() @api.onchange('early_quitting_date', 'contract_id') def _onchange_early_quitting_date(self): - if not self.early_quitting_date : - self.receivables_ids = [(5, 0, 0)] - return - + self.receivables_ids = [(5, 0, 0)] receivable_ids = self.env['property.accounts.receivable'].search([ ('due_date', '<=', self.early_quitting_date), ('contract_id', '=', self.contract_id.id), diff --git a/yuthon_property_approval/security/ir.model.access.csv b/yuthon_property_approval/security/ir.model.access.csv index 45f4d056f..32b5921b3 100644 --- a/yuthon_property_approval/security/ir.model.access.csv +++ b/yuthon_property_approval/security/ir.model.access.csv @@ -123,4 +123,5 @@ access_print_payment_wizard,print_payment_wizard,model_print_payment_wizard,base 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_action_refuse_wizard,action_refuse_wizard,model_action_refuse_wizard,base.group_user,1,1,1,1 -access_property_deposit_wizard,property_deposit_wizard,model_property_deposit_wizard,base.group_user,1,1,1,1 \ No newline at end of file +access_property_deposit_wizard,property_deposit_wizard,model_property_deposit_wizard,base.group_user,1,1,1,1 +access_deposit_reconciliation_line,deposit_reconciliation_line,model_deposit_reconciliation_line,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/yuthon_property_approval/views/inherit_yuthon_property_views.xml b/yuthon_property_approval/views/inherit_yuthon_property_views.xml index 3e647a83a..cb54d5d98 100644 --- a/yuthon_property_approval/views/inherit_yuthon_property_views.xml +++ b/yuthon_property_approval/views/inherit_yuthon_property_views.xml @@ -422,15 +422,7 @@ property.accounts.receivable - - - - - - - - - + diff --git a/yuthon_property_approval/views/property_lease_renewal.xml b/yuthon_property_approval/views/property_lease_renewal.xml index 724872812..9099b87b8 100644 --- a/yuthon_property_approval/views/property_lease_renewal.xml +++ b/yuthon_property_approval/views/property_lease_renewal.xml @@ -19,7 +19,7 @@ + invisible="urgency_level != 'normal'"/> - + @@ -83,34 +83,63 @@ + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - + diff --git a/yuthon_property_approval/wizard/property_deposit_wizard.py b/yuthon_property_approval/wizard/property_deposit_wizard.py index 56103a19f..d88b06e1a 100644 --- a/yuthon_property_approval/wizard/property_deposit_wizard.py +++ b/yuthon_property_approval/wizard/property_deposit_wizard.py @@ -9,580 +9,39 @@ _logger = logging.getLogger(__name__) class PropertyDepositWizard(models.Model): _name = 'property.deposit.wizard' - _description = '押金对账弹窗' + _description = '押金核销' - receivable_total = fields.Float(string='应收金额', compute="_compute_receivable_total", store=True) - bank_branch = fields.Char('银行名称') - warning_msg = fields.Char(string='警告') - bank_id = fields.Many2one('property.bank.statements', string='银行流水') - lessee_ids = fields.Many2many('property.tenant.information', string='承租方') - name_account = fields.Char( string='对方户名') - account = fields.Char(string='账号') - contract_ids = fields.Many2many('property.lease.contract', string='合同') - amount_been_reconciled = fields.Float(string='已对金额', store=True) - amount_water_been = fields.Float(string='已对流水金额', store=True) - amount_water = fields.Float(string='未对流水金额', store=True) - amount_reconciled = fields.Float(string='未核销', compute='_compute_receivable_total') - actual_total = fields.Float(string='实收金额') - actual_date = fields.Date(string='实收日期') - initial_total = fields.Float(string='初始金额', default=0.0) - rent = fields.Float(string='未收总租金', compute="_compute_rent_total") - penalty = fields.Float(string='未收总违约金', compute="_compute_penalty_total") - remake = fields.Text(string='摘要') + lessee_id = fields.Many2one('property.tenant.information', string='承租方') + contract_id = fields.Many2one('property.lease.contract', string='合同') + property_name = fields.Char(related="contract_id.property_name", string='物业信息') + actual_date = fields.Date(string='核销日期') + + deposit = fields.Float(string='押金') reconciliation_line_ids = fields.One2many('deposit.reconciliation.line', 'reconciliation_id', string='对账明细') - # @api.onchange('bank_id') - # def _onchange_bank_id(self): - # for rec in self: - # rec.lessee_ids = rec.bank_id.lessee_ids.ids - # rec.name_account = rec.bank_id.name_account - # rec.remake = rec.bank_id.remake - # rec.amount_water_been = rec.bank_id.yes_amount - # rec.amount_water = rec.bank_id.no_amount - # - # @api.depends('reconciliation_line_ids') - # def _compute_receivable_total(self): - # for rec in self: - # rec.receivable_total = sum(line.rent for line in rec.reconciliation_line_ids) - # rec.amount_reconciled = max(0 , rec.bank_id.no_amount - sum(line.amount for line in self.reconciliation_line_ids)) - # - # @api.depends('reconciliation_line_ids.collection_type', 'reconciliation_line_ids.rent') - # def _compute_rent_total(self): - # """计算所有类型为租金的明细行的未收总额""" - # for wizard in self: - # # 筛选出收款类型为租金的明细行,并累加其未收金额 - # rent_total = sum( - # line.rent or 0.0 - # for line in wizard.reconciliation_line_ids - # if line.collection_type == 'rent' - # ) - # wizard.rent = rent_total - # - # @api.depends('reconciliation_line_ids.collection_type', 'reconciliation_line_ids.rent') - # def _compute_penalty_total(self): - # """计算所有类型为违约金的明细行的未收总额""" - # for wizard in self: - # penalty_total = sum( - # line.rent or 0.0 - # for line in wizard.reconciliation_line_ids - # if line.collection_type == 'penalty' - # ) - # wizard.penalty = penalty_total - # - # @api.onchange('receivables_type_ids') - # def onchange_receivables_type_ids(self): - # for rec in self: - # selected_type_names = rec.receivables_type_ids.mapped('name') - # if not selected_type_names: - # rec.reconciliation_line_ids = [(5, 0, 0)] - # continue - # # 收款类型与名称的映射(包含违约金,但后续会排除) - # type_name_map = { - # 'deposit': '押金', - # 'rent': '租金', - # 'water_fee': '水费', - # 'lightbox': '灯箱', - # 'guanzijian': '管子间', - # 'electricity_fee': '电费', - # 'penalty': '违约金', - # 'management_fee': '管理费', - # 'repair_fee': '维修费', - # 'fee': '人工费/日光灯款/代垫用料费', - # } - # allowed_by_selection = [ - # coll_type for coll_type, name in type_name_map.items() - # if name in selected_type_names - # ] - # to_delete = [] - # for line in rec.reconciliation_line_ids: - # if line.collection_type not in allowed_by_selection: - # to_delete.append((2, line.id, 0)) - # - # if to_delete: - # rec.reconciliation_line_ids = to_delete - - # @api.onchange('actual_total') - # def compute_reconciliation_line_ids(self): - # type_name_map = { - # 'deposit': '押金', - # 'rent': '租金', - # 'water_fee': '水费', - # 'lightbox': '灯箱', - # 'guanzijian': '管子间', - # 'electricity_fee': '电费', - # 'penalty': '违约金', - # 'management_fee': '管理费', - # 'repair_fee': '维修费', - # 'fee': '人工费/日光灯款/代垫用料费', - # } - # new_lines = [] - # # 违约金生成 - # for contract_id in self.contract_ids: - # contract_penalties = [] - # if not contract_id.lease_ids: - # continue - # receivable_penalties = self.env['property.accounts.receivable'].search([ - # ('contract_id', '=', contract_id.id.origin), - # ('is_reconciliation', '=', False), - # ('is_invalid', '=', False), - # ('management_type_id.name', '=', '违约金') - # ]) - # for penalty in receivable_penalties: - # contract_penalties.append({ - # 'contract_id': penalty.contract_id.id, - # 'rent_month': 0, - # 'due_date': penalty.due_date, - # 'rent': penalty.submitted, - # 'amount': 0, - # 'collection_type': 'penalty', - # 'remake': penalty.remark, - # }) - # contract_receivables_ids = self.env['property.accounts.receivable'].search([ - # ('contract_id', '=', contract_id.id.origin), - # ('is_reconciliation', '=', False), - # ('is_invalid', '=', False), - # ('management_type_id.name', 'not in', ['违约金', '押金']), - # ]) - # for contract_receivables in contract_receivables_ids: - # if contract_receivables: - # # 计算应收日期的下月1号 - # if not contract_receivables.due_date: - # raise UserError(f'合同{contract_id.name}类型为{contract_receivables.management_type_id.name}的应收日期为空,请先填写应收日期') - # next_month_first = (fields.Date.from_string(contract_receivables.due_date) + - # relativedelta(months=1)).replace(day=1) - # # 检查是否产生违约 - # if fields.Date.from_string(self.actual_date) >= next_month_first: - # # 计算违约天数和违约金 - # late_days = (fields.Date.from_string(self.actual_date) - next_month_first).days + 1 - # # 找到符合月份的租金 - # penalty_amount = late_days * round( - # contract_receivables.submitted * float(contract_id.percentage_liquidated_damages.name), 2 - # ) - # start_date_str = fields.Date.to_string(next_month_first) - # end_date_str = self.actual_date - # # 处理新产生的违约金 - # if penalty_amount > 0: - # contract_penalties.append({ - # 'contract_id': contract_id.id, - # 'rent_month': 0, - # 'due_date': self.actual_date, - # 'rent': penalty_amount, - # 'amount': 0, - # 'collection_type': 'penalty', - # 'remake': f'违约金({start_date_str}至{end_date_str},共{late_days}天)', - # }) - # - # # 查找应收是否有未交全的租金 - # unpaid_rent_recs = self.env['property.accounts.receivable'].search([ - # ('management_type_id.name', 'not in', ['违约金', '押金']), - # ('contract_id', '=', contract_id.id.origin), - # ('is_invalid', '=', False), - # ('submitted', '!=', 0), - # ('payout_date', '!=', None), - # ]) - # if unpaid_rent_recs: - # for rent in unpaid_rent_recs: - # if rent.payout_date <= rent.due_date: # 跳过预收的租金 - # continue - # first_day = (rent.payout_date + timedelta(days=1)) - # last_month_rent = rent.submitted - # penalty_days = self.actual_date - first_day - # penalty_amount = (penalty_days.days + 1) * round( - # last_month_rent * float(contract_id.percentage_liquidated_damages.name), 2 - # ) if contract_id else 0.0 - # if penalty_amount > 0: - # new_lines.append((0, 0, { - # 'contract_id': contract_id.id if contract_id else None, - # 'rent_month': 0, - # 'due_date': fields.Date.to_string(first_day), - # 'rent': penalty_amount, - # 'amount': 0, - # 'collection_type': 'penalty', - # 'remake': f'违约金({fields.Date.to_string(first_day)}至{fields.Date.to_string(self.actual_date)},共{penalty_days.days + 1}天)', - # })) - # - # # 3. 添加到总列表 - # for vals in contract_penalties: - # new_lines.append((0, 0, vals)) - # - # if new_lines: - # keep_lines = [ - # (4, line.id) for line in self.reconciliation_line_ids - # if line.collection_type != 'penalty' - # ] - # self.reconciliation_line_ids = keep_lines + new_lines - # - # all_penalties = all(line.collection_type == 'penalty' for line in self.reconciliation_line_ids) - # if all_penalties and self.reconciliation_line_ids: - # total = sum(line.rent for line in self.reconciliation_line_ids) - # self.receivable_total = total - # - # # all_due_dates = [] - # # for line in self.reconciliation_line_ids: - # # if line.collection_type == 'rent' and line.due_date: - # # all_due_dates.append(fields.Date.from_string(line.due_date)) - # # # 2. 找到最早的到期日期 - # # if all_due_dates: - # # earliest_due_date = min(all_due_dates) - # # # 获取实际日期(假设actual_date是日期对象,如果是字符串需要先转换) - # # actual_date = fields.Date.from_string(self.actual_date) if isinstance(self.actual_date, - # # str) else self.actual_date - # # - # # # 计算起始月份(最早到期日的月份)和结束月份(实际日期的月份) - # # start_month = earliest_due_date + relativedelta(months=1) - # # end_month = actual_date - relativedelta(months=1) - # # - # # temp_rent_set = set() - # # target_rent_months = [] - # # - # # # 遍历从起始月到结束月的所有月份 - # # current_month = start_month - # # while current_month <= end_month: - # # due_month = current_month.strftime('%Y-%m') - # # # 为每个租金行添加当前月份的记录(去重) - # # for line in self.reconciliation_line_ids: - # # if line.collection_type == 'rent': - # # unique_key = ( - # # line.contract_id.id if line.contract_id else None, - # # due_month - # # ) - # # if unique_key not in temp_rent_set: - # # temp_rent_set.add(unique_key) - # # target_rent_months.append({ - # # 'contract_id': line.contract_id, - # # 'due_month': due_month - # # }) - # # # 移动到下个月 - # # if current_month.month == 12: - # # current_month = current_month.replace(year=current_month.year + 1, month=1, day=1) - # # else: - # # current_month = current_month.replace(month=current_month.month + 1, day=1) - # # else: - # # 没有符合条件的明细行时,返回空列表 - # target_rent_months = [] - # - # # if target_rent_months: - # # existing_penalty_months = set() - # # for line in self.reconciliation_line_ids: - # # if line.collection_type == 'penalty' and line.due_date: - # # penalty_month = fields.Date.from_string(line.due_date).strftime('%Y-%m') - # # penalty_key = ( - # # line.contract_id.id if line.contract_id else None, - # # penalty_month - # # ) - # # existing_penalty_months.add(penalty_key) - # # - # # # # 3. 补全缺失的跨月违约金(按合同+年月匹配,缺失则新增) - # # # missing_penalty_lines = [] - # # # for rent_info in target_rent_months: - # # # contract = rent_info['contract_id'] - # # # due_month = rent_info['due_month'] - # # # - # # # # 构建待检查的违约金key(合同+年月) - # # # check_key = ( - # # # contract.id if contract else None, - # # # due_month - # # # ) - # # # - # # # if check_key not in existing_penalty_months: - # # # year, month = map(int, due_month.split('-')) - # # # first_day = datetime(year, month, 1).date() - # # # penalty_days = (self.actual_date - first_day).days + 1 - # # # - # # # # 计算补全违约金的日期范围:当月1号 ~ 当月最后1天 - # # # # 查找上个月的租金明细行 - # # # if month == 1: - # # # last_month_year = year - 1 - # # # last_month = 12 - # # # else: - # # # last_month_year = year - # # # last_month = month - 1 - # # # last_month_str = f"{last_month_year}-{last_month:02d}" - # # # - # # # last_month_rent_line = next(( - # # # line for line in self.reconciliation_line_ids - # # # if line.collection_type == 'rent' - # # # and line.contract_id == contract - # # # and line.due_date # 有到期日期 - # # # and fields.Date.from_string(line.due_date).strftime('%Y-%m') == last_month_str - # # # ), None) - # # # # 提取上个月租金的rent金额 - # # # last_month_rent = last_month_rent_line.rent if last_month_rent_line else 0.0 - # # # penalty_amount = penalty_days * round( - # # # last_month_rent * float(contract.percentage_liquidated_damages.name), 2 - # # # ) if (contract and last_month_rent > 0) else 0.0 - # # # - # # # if penalty_amount > 0: - # # # missing_penalty_lines.append((0, 0, { - # # # 'contract_id': contract.id if contract else None, - # # # 'rent_month': 0, - # # # 'due_date': fields.Date.to_string(first_day), - # # # 'rent': penalty_amount, - # # # 'amount': 0, - # # # 'collection_type': 'penalty', - # # # 'remake': f'违约金({fields.Date.to_string(first_day)}至{fields.Date.to_string(self.actual_date)},共{penalty_days}天', - # # # })) - # # # - # # # # 添加缺失的违约金记录 - # # # if missing_penalty_lines: - # # # self.write({'reconciliation_line_ids': missing_penalty_lines}) - # - # self.create_reconciliation_vals() - # line_types = [] - # for line in self.reconciliation_line_ids: - # if line.collection_type not in line_types: - # line_types.append(line.collection_type) - # type_names = [type_name_map.get(t) for t in line_types if type_name_map.get(t)] - # receivable_types = self.env['yuthon.management.type'].search([ - # ('name', 'in', type_names) - # ]) - - def create_reconciliation_vals(self): - """ - 按已生成的明细行 分配金额(优先处理押金) - :return: - """ - remaining_amount = self.bank_id.no_amount - penalty_lines = [line for line in self.reconciliation_line_ids if line.collection_type == "penalty"] # 违约金行 - rent_lines = [line for line in self.reconciliation_line_ids if line.contract_id.contract_code == "(租)(管)TX2023-072"] # 租金行 - - type_priority = { - 'deposit': 1, # 押金优先 - 'rent': 2, # 其次租金 - 'other': 3, # 其他类型 - 'penalty': 4 - } - all_lines = sorted( - self.reconciliation_line_ids, - key=lambda line: ( - line.due_date.strftime('%Y-%m') if line.due_date else '', - type_priority.get(line.collection_type, 3) - ) - ) - for line in all_lines: - if remaining_amount <= 0: - break - if line.collection_type == "penalty": - continue - # 分配当前行金额 - line.amount = 0 - need_amount = round(line.rent or 0, 2) - assign_amount = min(need_amount, remaining_amount) - line.amount = assign_amount - remaining_amount -= assign_amount - if assign_amount > 0 and round(line.amount or 0, 2)>0: - line.onchange_remake() - - if remaining_amount > 0: - penalty_lines = [line for line in all_lines if line.collection_type == "penalty"] - for line in penalty_lines: - if remaining_amount <= 0: - break - line.amount = 0 - need_amount = line.rent or 0 - assign_amount = min(need_amount, remaining_amount) - line.amount = assign_amount - remaining_amount -= assign_amount - if assign_amount > 0 and round(line.amount or 0, 2) > 0: - line.onchange_remake() - - for line in penalty_lines: - line.color = 'blue' - - for line in rent_lines: - line.color = 'success' - - def confirm(self): - collection_type_mapping = { - 'deposit': '押金', - 'rent': '租金', - 'lightbox': '灯箱', - 'guanzijian': '管子间', - 'water_fee': '水费', - 'electricity_fee': '电费', - 'penalty': '违约金', - 'management_fee': '管理费', - 'repair_fee': '维修费', - 'other': '其他' - } - for rec in self: - bank_lines = self.env['property.bank.line'].search([ - ('bank', '=', rec.bank_id.name), - ('tenant_id.name', '!=', False) - ]) - if not bank_lines: - information = self.env['property.tenant.information'].search( - [('name', '=', rec.bank_id.name_account)], - limit=1) - if information: - bank_lines = self.env['property.bank.line'].create({ - 'tenant_id': information.id, - 'bank': rec.bank_id.name, - 'user': information.name, - 'name': rec.bank_id.bank_branch, - }) - remaining_amount = 0 - for line in rec.reconciliation_line_ids: - remaining_amount += line.amount - - if round(rec.amount_water, 2) > round(remaining_amount, 2): - rec.bank_id.registered = rec.amount_water - remaining_amount - rec.bank_id.bank_state = 'registered' - else: - rec.bank_id.registered = 0 - rec.bank_id.bank_state = 'reconciled' - rec.bank_id.reconciliation_time = fields.Datetime.now() - timedelta(hours=8) - yes_amount = 0 - for line in rec.reconciliation_line_ids: - # 跳过金额为0的行 - if (round(line.amount or 0, 2) == 0 and line.collection_type != 'penalty') or \ - (line.contract_id and line.contract_id.is_contract): - continue - first_day = line.due_date.replace(day=1) - last_day = (first_day + relativedelta(months=1)) - relativedelta(days=1) - if line.due_date >= self.actual_date and line.collection_type == 'deposit': - domain = [ - ('management_type_id.name', '=', collection_type_mapping.get(line.collection_type)), - ('is_reconciliation', '=', False), - ('is_suspend', '=', False), - ] - else: - domain = [ - ('management_type_id.name', '=', collection_type_mapping.get(line.collection_type)), - ('is_reconciliation', '=', False), - ('is_suspend', '=', False), - ('due_date', '>=', first_day), - ('due_date', '<=', last_day) - ] - if line.contract_id: - domain.append(('contract_id', '=', line.contract_id.id)) - receivable_ids = self.env['property.accounts.receivable'].search(domain) - for receivable in receivable_ids: - if round(line.rent or 0, 2) == round(line.amount or 0, 2): - is_reconciliation = True - else: - is_reconciliation = False - if receivable and receivable.remark: - final_remark = f"{receivable.remark}\n{line.remake}" - else: - final_remark = line.remake - if line.collection_type != 'penalty': - receivable.delivered1 = receivable.delivered1 + line.amount - receivable.write({ - 'is_reconciliation': is_reconciliation, - 'payout_date': rec.actual_date, - 'remark': final_remark, - 'reconciliation_time': fields.Datetime.now(), - 'receivable_log': f'当前登录人{rec.env.user.name}于{fields.Datetime.now()}对户名为{rec.bank_id.name_account}进行对账操作,对账应收金额为{rec.receivable_total},实际金额为{rec.amount_water}' - }) - receivable.contract_id.update_contact_state() - self.env['receivable.modify.line'].create({ - 'bank_id': rec.bank_id.id, - 'not_collected': line.rent, - 'reconciliation_date': rec.actual_date if line.amount != 0 else None, - 'receivable_id': receivable.id, - 'is_write': True if line.amount != 0 else False, - 'is_penalty': False, - 'amount': line.amount, - }) - - management_type_id = self.env['yuthon.management.type'].search([('name', '=', '违约金')]) - if line.collection_type == 'penalty': - penalty_date = line.due_date - first_day_of_last_month = penalty_date.replace(day=1) - relativedelta(months=2) - # 上个月最后一天:当前月第一天减1天 - last_day_of_last_month = penalty_date.replace(day=1) - relativedelta(days=1) - is_create = self.env['deposit.reconciliation.line'].search([ - ('reconciliation_id', '=', self.id), - ('contract_id', '=', line.contract_id.id ), - ('due_date', '>=', first_day_of_last_month), - ('due_date', '<=', last_day_of_last_month), - ('collection_type', '!=', 'penalty'), - ('amount', '>', 0) - ]) - # 用备注的的开始日期和结束日期 - date_pattern = r'(\d{4}-\d{2}-\d{2})[至-](\d{4}-\d{2}-\d{2})' - date_match = re.search(date_pattern, line.remake or '') - extract_start_date = date_match.group(1) if date_match else line.due_date - extract_end_date = date_match.group(2) if date_match else None - check_domain = [ - ('due_date', '=', line.due_date), - ('contract_id', '=', line.contract_id.id), - ('submitted', '=', line.rent), - ('is_invalid', '=', False), - ('management_type_id', '=', management_type_id.id), - ] - # 检查是否已有相同记录 - existing_record = self.env['property.accounts.receivable'].search(check_domain) - is_reconciliation = round(line.amount,2) == round(line.rent, 2) - receivable_id = self.env['property.accounts.receivable'] - if not existing_record and is_create: - domain2 = [ - ('due_date', '=', line.due_date), - ('contract_id', '=', line.contract_id.id), - ('collection_type', '=', 'rent'), - ] - extract_start_date = datetime.strptime(extract_start_date, "%Y-%m-%d").date() - extract_end_date = datetime.strptime(extract_end_date, "%Y-%m-%d").date() - reconciliation_id = self.env['deposit.reconciliation.line'].search(domain2, limit=1) - receivable_id = self.env['property.accounts.receivable'].create({ - 'is_reconciliation': is_reconciliation, - 'due_date': extract_end_date, - 'due_date_end': None, - 'penalty': reconciliation_id.rent, - 'management_type_id': management_type_id.id, - 'contract_id': line.contract_id.id, - 'delivered1': line.amount, - 'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None, - 'original_amount': line.rent, - 'remark': line.remake, - 'reconciliation_time': fields.Datetime.now(), - 'charging_range': f"{extract_start_date} 到 {extract_end_date}共{(extract_end_date - extract_start_date).days + 1}" - }) - else: - if line.amount: - existing_record.write({ - 'delivered1': existing_record.delivered1 + line.amount, - 'due_date': extract_end_date, - 'due_date_end': None, - 'is_reconciliation': is_reconciliation, - 'payout_date': rec.actual_date if (line.rent == line.amount and line.rent != 0) else None, - 'remark': line.remake, - 'bank_id': rec.bank_id.id if line.amount != 0 else None, - 'reconciliation_time': fields.Datetime.now(), - }) - - if existing_record: - receivable_id = existing_record - - if receivable_id: - self.env['receivable.modify.line'].create({ - 'bank_id': rec.bank_id.id, - 'not_collected': line.rent, - 'is_write': True if line.amount != 0 else False, - 'is_penalty': True, - 'reconciliation_date': rec.actual_date if line.amount != 0 else None, - 'receivable_id': receivable_id.id, - 'amount': line.amount, - }) - - rec.amount_been_reconciled += line.amount - yes_amount += line.amount - - rec.bank_id.yes_amount = rec.bank_id.yes_amount + yes_amount - rec.bank_id.change_amount = sum(line.amount for line in rec.reconciliation_line_ids) - - all_contracts = self.contract_ids - remaining_contract_ids = [] - for contract in all_contracts: - unreconciled_receivables = self.env["property.accounts.receivable"].search([ - ('contract_id', '=', contract.id), - ('is_reconciliation', '=', False), - ('is_invalid', '=', False), - ], limit=1) - if unreconciled_receivables: - remaining_contract_ids.append(contract.id) + pass + # for record in self: + # reconciliation_lines = record.reconciliation_line_ids + # total_amount = sum(line.amount for line in reconciliation_lines) + # total_write_amount = sum(line.write_amount for line in reconciliation_lines) + # + # amount_equal = abs(total_amount - total_write_amount) < 1e-6 + # for line in reconciliation_lines: + # if line.write_amount: + # if line.receivable_id: + # if line.write_amount == line.amount: + # line.receivable_id.is_reconciliation = True + # else: + # line.receivable_id.yes_amount += line.write_amount + # if line.bills_id: + # line.bills_id.actual_date = record.actual_date + # line.bills_id.amount_actual = line.write_amount + # if amount_equal: + # if record.contract_id: + # record.contract_id.contact_state = 'settle' + # record.has_date = record.actual_date + # record.bills_list_id.contract_id.compute_is_has() class DepositReconciliationLine(models.Model): @@ -591,58 +50,20 @@ class DepositReconciliationLine(models.Model): _order = 'due_date desc' reconciliation_id = fields.Many2one('property.deposit.wizard', string='关联押金对账单弹窗') - sequence = fields.Integer(string='顺序') - property_name = fields.Text(string='物业', compute="_compute_property_name") - property_ids = fields.Many2many('yuthon.property', string='物业', compute="_compute_property_ids", store=True) - contract_id = fields.Many2one('property.lease.contract', string='合同编号') - rent_month = fields.Float(string='当期金额') + receivable_id = fields.Many2one('property.accounts.receivable', string='关联应收账单') + bill_id = fields.Many2one('property.bills.list', string='关联应付账单') + due_date = fields.Date(string='应收日期') - rent = fields.Float(string='未收') + management_type_id = fields.Many2one('yuthon.management.type', string='类型') + payment_type = fields.Selection([('pay', '支付'), ('spending', '支出')], string='收付款类型') amount = fields.Float(string='金额', store=True) - collection_type = fields.Selection([('deposit', '押金'), ('rent', '租金'), ('water_fee', '水费'), ('lightbox', '灯箱'), ('guanzijian', '管子间'), - ('electricity_fee', '电费'), ('penalty', '违约金'), ('management_fee', '管理费'), ('repair_fee', '维修费'), ('fee', '人工费/日光灯款/代垫用料费'), ('rename_fee', '更名费'), ('service', '有偿服务收入')], string='收款类型', store=True) + write_amount = fields.Float(string='核销金额') remake = fields.Text(string='备注') - color = fields.Selection([('success', '绿'), ('blue', '蓝')], string='颜色') - @api.depends('contract_id') - def _compute_contract_name(self): - for record in self: - if record.contract_id and record.contract_id.version: - record.contract_name = record.contract_id.contract_code + record.contract_id.version - - @api.depends('contract_id') - def _compute_property_name(self): - for record in self: - record.property_name = record.contract_id.property_name - - @api.depends('contract_id', 'contract_id.lease_ids', 'contract_id.lease_ids.address_id') - def _compute_property_ids(self): - for record in self: - property_ids = self.env['yuthon.property'] - if record.contract_id and record.contract_id.lease_ids: - property_ids = record.contract_id.lease_ids.mapped('address_id') - record.property_ids = property_ids - - @api.onchange('amount', 'due_date', 'rent', 'collection_type', 'reconciliation_id.actual_date') + @api.onchange('write_amount') def onchange_remake(self): for line in self: - actual_date = fields.Date.to_date(line.reconciliation_id.bank_id.start_time + relativedelta(hours=8)) - if round(line.amount or 0, 2) == 0 and line.collection_type != 'penalty': - line.remake = "" - return - if line.amount and line.collection_type != 'penalty': - due_date = line.due_date - actual_year, actual_month = actual_date.year, actual_date.month - due_year, due_month = due_date.year, due_date.month - due_date_str = due_date.strftime('%Y-%m') - actual_date_str = actual_date.strftime('%Y-%m-%d') - rent = round(line.rent or 0, 2) - if (due_month > actual_month and due_year == actual_year) or (due_year > actual_year): - if rent >= line.amount: - line.remake = f'{actual_date_str}预{due_date_str},欠{round(rent - line.amount, 2)}' - elif due_month == actual_month and due_year == actual_year: - if rent >= line.amount: - line.remake = f'交{due_date_str},欠{round(rent - line.amount, 2)}' - elif (due_month < actual_month and due_year == actual_year) or (due_year < actual_year): - if rent >= line.amount: - line.remake = f'{actual_date_str}补{due_date_str},欠{round(rent - line.amount, 2)}' \ No newline at end of file + if line.write_amount and line.payment_type == 'pay' and line.management_type_id.name != '违约金': + line.remake = '押金转'+ line.management_type_id.name + elif line.write_amount and line.payment_type == 'spending': + line.remake = '退' + line.management_type_id.name \ No newline at end of file diff --git a/yuthon_property_approval/wizard/property_deposit_wizard_views.xml b/yuthon_property_approval/wizard/property_deposit_wizard_views.xml index 543dcc516..5d7366a1e 100644 --- a/yuthon_property_approval/wizard/property_deposit_wizard_views.xml +++ b/yuthon_property_approval/wizard/property_deposit_wizard_views.xml @@ -4,53 +4,38 @@ property.deposit.wizard.form property.deposit.wizard - + - - - - - - + + + + + + - + - + - + - - - - - - - - - - + - - - + - - - - - - - - + + + + + + + From fc3c594571aa0be96209f33ea76352724920c0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Sat, 20 Dec 2025 14:30:27 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wizard/property_deposit_wizard.py | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/yuthon_property_approval/wizard/property_deposit_wizard.py b/yuthon_property_approval/wizard/property_deposit_wizard.py index d88b06e1a..8a36790a8 100644 --- a/yuthon_property_approval/wizard/property_deposit_wizard.py +++ b/yuthon_property_approval/wizard/property_deposit_wizard.py @@ -20,28 +20,27 @@ class PropertyDepositWizard(models.Model): reconciliation_line_ids = fields.One2many('deposit.reconciliation.line', 'reconciliation_id', string='对账明细') def confirm(self): - pass - # for record in self: - # reconciliation_lines = record.reconciliation_line_ids - # total_amount = sum(line.amount for line in reconciliation_lines) - # total_write_amount = sum(line.write_amount for line in reconciliation_lines) - # - # amount_equal = abs(total_amount - total_write_amount) < 1e-6 - # for line in reconciliation_lines: - # if line.write_amount: - # if line.receivable_id: - # if line.write_amount == line.amount: - # line.receivable_id.is_reconciliation = True - # else: - # line.receivable_id.yes_amount += line.write_amount - # if line.bills_id: - # line.bills_id.actual_date = record.actual_date - # line.bills_id.amount_actual = line.write_amount - # if amount_equal: - # if record.contract_id: - # record.contract_id.contact_state = 'settle' - # record.has_date = record.actual_date - # record.bills_list_id.contract_id.compute_is_has() + for record in self: + reconciliation_lines = record.reconciliation_line_ids + total_amount = sum(line.amount for line in reconciliation_lines) + total_write_amount = sum(line.write_amount for line in reconciliation_lines) + + amount_equal = abs(total_amount - total_write_amount) < 1e-6 + for line in reconciliation_lines: + if line.write_amount: + if line.receivable_id: + if line.write_amount == line.amount: + line.receivable_id.is_reconciliation = True + else: + line.receivable_id.yes_amount += line.write_amount + if line.bills_id: + line.bills_id.actual_date = record.actual_date + line.bills_id.amount_actual = line.write_amount + if amount_equal: + if record.contract_id: + record.contract_id.contact_state = 'settle' + record.has_date = record.actual_date + record.bills_list_id.contract_id.compute_is_has() class DepositReconciliationLine(models.Model): From 44ff44fdbdb2d0b20801fb236188aa022d6401c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Sat, 20 Dec 2025 15:09:26 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_issue/views/issue_approval_views.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/yuthon_issue/views/issue_approval_views.xml b/yuthon_issue/views/issue_approval_views.xml index d5db01584..b13167ebf 100644 --- a/yuthon_issue/views/issue_approval_views.xml +++ b/yuthon_issue/views/issue_approval_views.xml @@ -40,6 +40,7 @@ + From 2d93c90b5c2f23e4ba1cd3c6dfe4fccf04d870f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Sat, 20 Dec 2025 19:11:24 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yuthon_holiday/models/inherit_hr_leave.py | 18 +++++ yuthon_holiday/views/hr_leave_batch.xml | 4 +- yuthon_hr_attendance/models/hr_attendance.py | 21 ++--- .../models/processing_order_approval.py | 14 ++-- .../views/property_device_repair_views.xml | 1 + .../views/property_report_repair.xml | 1 + .../models/property_meter_archives.py | 2 + .../models/property_meter_water.py | 2 + .../views/property_meter_archives_views.xml | 2 + .../views/property_meter_water_views.xml | 2 + .../wizard/electric_water_wizard.py | 25 ++++++ yuthon_property/wizard/meter_price_wizard.py | 3 +- yuthon_property/wizard/water_price_wizard.py | 2 +- .../models/property_lease_contract.py | 27 +++---- .../views/property_lease_contract.xml | 20 +++-- .../models/inherit_hr_payroll_structure.py | 25 ++++-- .../inherit_hr_payroll_structure_views.xml | 3 +- yuthon_salary/wizard/yuthon_payroll_wizard.py | 79 ++++++++++++------- 18 files changed, 172 insertions(+), 79 deletions(-) diff --git a/yuthon_holiday/models/inherit_hr_leave.py b/yuthon_holiday/models/inherit_hr_leave.py index 429e9b969..cb2c3c3e0 100644 --- a/yuthon_holiday/models/inherit_hr_leave.py +++ b/yuthon_holiday/models/inherit_hr_leave.py @@ -314,6 +314,8 @@ class HolidaysRequestBatch(models.Model): is_issues = fields.Boolean(string='是否上会') work_end = fields.Boolean(string="结束") issues_result = fields.Html(string="会议结果") + entrusted_agent_id = fields.Many2one('res.users', string="委托代理人") + is_entrusted = fields.Boolean(string="是否生成工作流委托") leave_by_hour = fields.Boolean(string='请假单位(小时)', default=False) leave_by_day = fields.Boolean(string='请假单位(天)', default=True) urgency_level = fields.Selection([('normal', '普件'), ('urgent', '急件'), ('emergency', '特急件')], string='紧急度', @@ -357,6 +359,22 @@ class HolidaysRequestBatch(models.Model): # 提交 def action_submit(self): + if self.is_entrusted: + leave_batch_model = self.env['ir.model'].search( + [('model', '=', 'hr.leave.batch')], + limit=1 + ) + entrust_vals = { + 'model_ids': [(6, 0, [leave_batch_model.id])], + 'entrust_user_id': self.users_id.id, + 'entrust_user_id2': self.entrusted_agent_id.id, + 'is_expiration_date': True, + 'start_date': fields.Date.today(), + 'end_date': False, + 'entrust_notice': True, + } + self.env['soong.workflow.entrust'].create(entrust_vals) + self.leave_ids.action_validate() self.hour_leave_ids.action_validate() """提交请假申请,区分年假和其他假期处理""" diff --git a/yuthon_holiday/views/hr_leave_batch.xml b/yuthon_holiday/views/hr_leave_batch.xml index e9d0609e3..8487e2de7 100644 --- a/yuthon_holiday/views/hr_leave_batch.xml +++ b/yuthon_holiday/views/hr_leave_batch.xml @@ -44,6 +44,9 @@ + + + @@ -239,7 +242,6 @@ - diff --git a/yuthon_hr_attendance/models/hr_attendance.py b/yuthon_hr_attendance/models/hr_attendance.py index 1e95b1b8b..9d6d5f671 100644 --- a/yuthon_hr_attendance/models/hr_attendance.py +++ b/yuthon_hr_attendance/models/hr_attendance.py @@ -814,16 +814,17 @@ class HrAttendance(models.Model): if is_executive: domain = [('current_data', '=', current_date)] attendances = self.env['hr.attendance'].search(domain) - attended_employee_ids = attendances.mapped('employee_id') - for employee in attended_employee_ids: - if is_check_in: - is_card = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('check_in', '!=', False), ('current_data', '=', current_date)], limit=1) - if not is_card: - self.card_in_comment(employee, '行政班次') - else: - is_card = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('check_out', '!=', False), ('current_data', '=', current_date)], limit=1) - if not is_card: - self.card_out_comment(employee, '行政班次') + if self._process_shift_days(current_date): # 如果是工作日则提醒 + attended_employee_ids = attendances.mapped('employee_id') + for employee in attended_employee_ids: + if is_check_in: + is_card = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('check_in', '!=', False), ('current_data', '=', current_date)], limit=1) + if not is_card: + self.card_in_comment(employee, '行政班次') + else: + is_card = self.env['hr.attendance'].search([('employee_id', '=', employee.id), ('check_out', '!=', False), ('current_data', '=', current_date)], limit=1) + if not is_card: + self.card_out_comment(employee, '行政班次') else: slot_start = datetime.combine(current_date, datetime.min.time()) slot_end = datetime.combine(current_date + timedelta(days=1), datetime.min.time()) diff --git a/yuthon_maintain/models/processing_order_approval.py b/yuthon_maintain/models/processing_order_approval.py index cfaa0c67b..b6a6f7bbd 100644 --- a/yuthon_maintain/models/processing_order_approval.py +++ b/yuthon_maintain/models/processing_order_approval.py @@ -70,13 +70,15 @@ class ProcessingOrderApproval(models.Model): name_title = fields.Char(default=_get_default_name_title) - @api.depends("company_id") + @api.depends("company_id", "repair_id", "device_id", "property_id") def _compute_name(self): - self.name = False - if self.repair_id: - self.name = self.property_id.name + '-处理单' - if self.device_id: - self.name = self.device_id.name + '-处理单' + for record in self: + if record.repair_id and record.property_id: + record.display_name = f"{record.property_id.name}-处理单" + elif record.device_id: + record.display_name = f"{record.device_id.name}-处理单" + else: + record.display_name = False @api.depends('property_id') def _compute_contract_id(self): diff --git a/yuthon_maintain/views/property_device_repair_views.xml b/yuthon_maintain/views/property_device_repair_views.xml index f4b7e1ae8..012f7271a 100644 --- a/yuthon_maintain/views/property_device_repair_views.xml +++ b/yuthon_maintain/views/property_device_repair_views.xml @@ -10,6 +10,7 @@ + diff --git a/yuthon_maintain/views/property_report_repair.xml b/yuthon_maintain/views/property_report_repair.xml index d8a85f9ca..447a3fe9c 100644 --- a/yuthon_maintain/views/property_report_repair.xml +++ b/yuthon_maintain/views/property_report_repair.xml @@ -12,6 +12,7 @@ + diff --git a/yuthon_property/models/property_meter_archives.py b/yuthon_property/models/property_meter_archives.py index 6308237ee..4d1ca9aea 100644 --- a/yuthon_property/models/property_meter_archives.py +++ b/yuthon_property/models/property_meter_archives.py @@ -262,6 +262,8 @@ class MeterArchivesLine(models.Model): type = fields.Selection([("public", "公共"), ("fees", "收费")], string="类型") employee_ids = fields.Many2many("hr.employee", string="抄表人") remarks = fields.Text(string="备注") + is_fix_loss = fields.Selection([('yes', '有'), ('no', '无')], default="yes", string="有无线损") + is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价") archives_state = fields.Selection([("stop", "停用"), ("use", "正常"), ("rent", "出租")], string="状态", default="use",) state = fields.Selection([("reading", "已抄"), ("unreading", "未抄")], string="抄表状态", default="unreading",) maximum_count = fields.Integer(string="最大位数") diff --git a/yuthon_property/models/property_meter_water.py b/yuthon_property/models/property_meter_water.py index 58c04165c..ff3fb6099 100644 --- a/yuthon_property/models/property_meter_water.py +++ b/yuthon_property/models/property_meter_water.py @@ -227,6 +227,8 @@ class MeterWaterLine(models.Model): remarks = fields.Text(string='备注') employee_ids = fields.Many2many('hr.employee', string='抄表人') maximum_count = fields.Integer(string='最大位数') + is_fix_loss = fields.Selection([('yes', '有'), ('no', '无')], default="yes", string="有无线损") + is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价") is_push = fields.Boolean(string='推送', default=False) @api.model_create_multi diff --git a/yuthon_property/views/property_meter_archives_views.xml b/yuthon_property/views/property_meter_archives_views.xml index 0b1fb307d..8ee5e10fc 100644 --- a/yuthon_property/views/property_meter_archives_views.xml +++ b/yuthon_property/views/property_meter_archives_views.xml @@ -118,6 +118,8 @@ + + diff --git a/yuthon_property/views/property_meter_water_views.xml b/yuthon_property/views/property_meter_water_views.xml index 83ec9add6..c440d48e2 100644 --- a/yuthon_property/views/property_meter_water_views.xml +++ b/yuthon_property/views/property_meter_water_views.xml @@ -120,6 +120,8 @@ + + diff --git a/yuthon_property/wizard/electric_water_wizard.py b/yuthon_property/wizard/electric_water_wizard.py index 0ac1a1645..a6f1c3a5c 100644 --- a/yuthon_property/wizard/electric_water_wizard.py +++ b/yuthon_property/wizard/electric_water_wizard.py @@ -36,6 +36,17 @@ class ElectricWaterWizard(models.TransientModel): else: contract_line = False + electric_id = self.env['property.electric.fee.line'].search([ + ('property_meter_archives_id', '=', meter.id), + ('contract_id', '!=', False), + ], limit=1) + if electric_id: + is_fix_loss = electric_id.is_fix_loss + is_fix_price = electric_id.is_fix_price + else: + is_fix_loss = 'yes' + is_fix_price = 'no' + # 查找上个月的记录 # 查找上个月的记录,上次用量,上次读数,上次抄表日期 last_month_line = meter.line_ids.search([('meter_date', '<', self.push_date)]).sorted('meter_date')[-1] @@ -46,6 +57,8 @@ class ElectricWaterWizard(models.TransientModel): meter.line_ids.create({ 'archives_id': meter.id, 'is_acc': is_acc, + 'is_fix_loss':is_fix_loss, + 'is_fix_price':is_fix_price, 'last_meter': last_month_line.last_meter, 'last_read': last_month_line.last_read, 'last_dosage': last_month_line.last_dosage, @@ -87,6 +100,16 @@ class ElectricWaterWizard(models.TransientModel): else: contract_line = False + water_id = self.env['property.water.fee.line'].search([('property_meter_water_id', '=', water.id), + ('contract_id', '!=', False), + ], limit=1) + if water_id: + is_fix_loss = water_id.is_fix_loss + is_fix_price = water_id.is_fix_price + else: + is_fix_loss = 'yes' + is_fix_price = 'no' + # 查找上个月的记录,上次用量,上次读数,上次抄表日期 last_month_line = water.line_ids.search([('meter_date', '<', self.push_date)]).sorted('meter_date')[-1] # 如果当前数据不存在,则新建任务,按照天的纬度 @@ -94,6 +117,8 @@ class ElectricWaterWizard(models.TransientModel): if not self_month_line: water.line_ids.create({ 'water_id': water.id, + 'is_fix_loss': is_fix_loss, + 'is_fix_price': is_fix_price, 'last_meter': last_month_line.last_meter, 'last_read': last_month_line.last_read, 'last_dosage': last_month_line.last_dosage, diff --git a/yuthon_property/wizard/meter_price_wizard.py b/yuthon_property/wizard/meter_price_wizard.py index 4f726735d..1563e8c31 100644 --- a/yuthon_property/wizard/meter_price_wizard.py +++ b/yuthon_property/wizard/meter_price_wizard.py @@ -33,7 +33,6 @@ class MeterPriceWizard(models.TransientModel): ('select_time', '=', str(last_month)), ('meter_type', '=', 'electric') ], limit=1) - # 3. 赋值价格 self.price = logs_id.price if logs_id else 0.0 self.line_loss = logs_id.line_loss if logs_id else 0.0 @@ -74,7 +73,7 @@ class MeterPriceWizard(models.TransientModel): valid.write({ 'unit': unit, - 'line_loss': self.line_loss + 'line_loss': self.line_loss if valid.is_fix_loss == 'yes' else 0 }) valid.onchange_receivables() diff --git a/yuthon_property/wizard/water_price_wizard.py b/yuthon_property/wizard/water_price_wizard.py index 3cf158e5e..46bf06650 100644 --- a/yuthon_property/wizard/water_price_wizard.py +++ b/yuthon_property/wizard/water_price_wizard.py @@ -75,7 +75,7 @@ class WaterPriceWizard(models.TransientModel): valid.write({ 'unit': unit, - 'line_loss': self.line_loss, + 'line_loss': self.line_loss if valid.is_fix_loss == 'yes' else 0 }) valid.onchange_receivables() diff --git a/yuthon_property_approval/models/property_lease_contract.py b/yuthon_property_approval/models/property_lease_contract.py index 9717f24aa..0b6853224 100644 --- a/yuthon_property_approval/models/property_lease_contract.py +++ b/yuthon_property_approval/models/property_lease_contract.py @@ -2421,15 +2421,14 @@ class PropertyWaterFeeLine(models.Model): start_date = fields.Date(related='contract_id.term_start_time', string="开始日期") end_date = fields.Date(related='contract_id.term_end_time', string="结束日期") unit_price = fields.Float(string='固定单价', digits=(16, 4)) - line_loss = fields.Float(string='线损') + is_fix_loss = fields.Selection([('yes', '有'), ('no', '无')], default="yes", string="有无线损") + is_fix_price = fields.Selection([('yes', '是'), ('no', '否')], default="no", string="是否固定单价") epitope_values_id = fields.Many2one(related='property_meter_water_id.epitope_values_id', string="表位置") - @api.constrains('line_loss') - def _check_line_loss(self): - for rec in self: - if rec.property_meter_water_id.line_ids: - for line in rec.property_meter_water_id.line_ids: - line.line_loss = rec.line_loss + @api.onchange('is_fix_price') + def _onchange_is_fix_price(self): + if self.is_fix_price == 'no': + self.unit_price = 0 class PropertyElectricFeeLine(models.Model): @@ -2443,16 +2442,14 @@ class PropertyElectricFeeLine(models.Model): start_date = fields.Date(related='contract_id.term_start_time', string="开始日期") end_date = fields.Date(related='contract_id.term_end_time', string="结束日期") unit_price = fields.Float(string='固定单价', digits=(16, 4)) - line_loss = fields.Float(string='线损') + is_fix_loss = fields.Selection([('yes', '有'), ('no', '无')], default="no", string="有无线损") + is_fix_price = fields.Selection([('yes', '是'),('no', '否')], default="no", string="是否固定单价") epitope_values_id = fields.Many2one(related='property_meter_archives_id.epitope_values_id', string="表位置") - @api.constrains('line_loss') - def _check_line_loss(self): - for rec in self: - if rec.property_meter_archives_id.line_ids: - for line in rec.property_meter_archives_id.line_ids: - line.line_loss = rec.line_loss - + @api.onchange('is_fix_price') + def _onchange_is_fix_price(self): + if self.is_fix_price == 'no': + self.unit_price = 0 class PropertyOtherFeeLine(models.Model): _name = 'property.other.fee.line' diff --git a/yuthon_property_approval/views/property_lease_contract.xml b/yuthon_property_approval/views/property_lease_contract.xml index a0cef6227..b3c1a8ad7 100644 --- a/yuthon_property_approval/views/property_lease_contract.xml +++ b/yuthon_property_approval/views/property_lease_contract.xml @@ -279,8 +279,9 @@ - - + + + @@ -294,8 +295,9 @@ - - + + + @@ -819,8 +821,9 @@ - - + + + @@ -833,8 +836,9 @@ - - + + + diff --git a/yuthon_salary/models/inherit_hr_payroll_structure.py b/yuthon_salary/models/inherit_hr_payroll_structure.py index d06753286..facc430c5 100644 --- a/yuthon_salary/models/inherit_hr_payroll_structure.py +++ b/yuthon_salary/models/inherit_hr_payroll_structure.py @@ -1,8 +1,7 @@ from odoo import api, fields, models, _ import re from pypinyin import pinyin, Style -from datetime import date - +from datetime import date, datetime class HrPayrollStructure(models.Model): @@ -18,7 +17,7 @@ class HrPayrollStructure(models.Model): sequence = fields.Char(string='级别序号', store=True) create_time = fields.Date(string="生效日期", default=fields.Date.today()) end_time = fields.Date(string="失效日期") - is_enable = fields.Boolean(string="启用", default=True) + is_enable = fields.Boolean(string="启用", compute= "_compute_is_enable", store=True) currency_id = fields.Many2one("res.currency", string="货币", required=True, default=lambda self: self.env.company.currency_id) base_salary = fields.Monetary(string="基本工资", currency_field="currency_id") rank_salary = fields.Monetary(string="职级工资", currency_field="currency_id") @@ -29,6 +28,17 @@ class HrPayrollStructure(models.Model): advance_performance_bonuses = fields.Monetary(string="预发绩效奖金", currency_field="currency_id") renk_log_ids = fields.One2many("rank.log.line", "structure_id", string="职级日志明细行") + @api.depends('create_time', 'end_time') + def _compute_is_enable(self): + today = fields.Date.today() + for record in self: + if not record.create_time: + record.is_enable = False + elif not record.end_time: + record.is_enable = record.create_time <= today + else: + record.is_enable = record.create_time <= today <= record.end_time + @api.model def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True): res = super(HrPayrollStructure, self).read_group(domain, fields, groupby, offset=offset, limit=limit, orderby=orderby, @@ -196,6 +206,7 @@ class HrPayrollStructure(models.Model): for rule in payroll.rule_ids: rule.sudo().unlink() + def update_payroll_items(self): payroll_ids = self.env["hr.payroll.structure"].browse( self._context.get('active_ids', self._context.get('active_id'))) @@ -238,14 +249,12 @@ class HrPayrollStructure(models.Model): payroll.renk_log_ids.unlink() def onetime(self): + dec_2025_start = datetime(2025, 10, 31).date() payroll_ids = self.env["hr.payroll.structure"].browse( self._context.get('active_ids', self._context.get('active_id'))) for payroll in payroll_ids: - create_time = payroll.renk_log_ids[-1].effective_date - payroll.write({ - 'create_time': create_time, - }) - + payroll.end_time = dec_2025_start + payroll.is_enable = False def rank_log_time(self): """定时任务:根据日志记录的生效时间更新主模型字段值""" diff --git a/yuthon_salary/views/inherit_hr_payroll_structure_views.xml b/yuthon_salary/views/inherit_hr_payroll_structure_views.xml index 24236af8c..cd55abf6a 100644 --- a/yuthon_salary/views/inherit_hr_payroll_structure_views.xml +++ b/yuthon_salary/views/inherit_hr_payroll_structure_views.xml @@ -100,6 +100,7 @@ + @@ -112,7 +113,7 @@ 职级 hr.payroll.structure tree,form,search - + {'search_default_is_enable':1} 批量更新职级类型 diff --git a/yuthon_salary/wizard/yuthon_payroll_wizard.py b/yuthon_salary/wizard/yuthon_payroll_wizard.py index bb5c7ce34..e6395491a 100644 --- a/yuthon_salary/wizard/yuthon_payroll_wizard.py +++ b/yuthon_salary/wizard/yuthon_payroll_wizard.py @@ -24,19 +24,7 @@ class YuthonPayrollWizard(models.Model): sh = book.sheet_by_index(0) for rx in range(1, sh.nrows): row = sh.row(rx) - self.yuthon_payroll_wg_data(row) - # if sh.row(1)[6].value == 'thtz': - # salary_rule_model = self.env['hr.salary.rule'] - # all_rules = salary_rule_model.search([]) - # all_rules.unlink() - # for rx in range(1, sh.nrows): - # row = sh.row(rx) - # if row[6].value == 'thtz': - # self.yuthon_payroll_tt_data(row) - # elif row[6].value == 'thwg': - # self.yuthon_payroll_wg_data(row) - # elif row[6].value == 'thlc': - # self.yuthon_payroll_lc_data(row) + self.yuthon_payroll_lc_data(row) def yuthon_payroll_tt_data(self, row): type_id = self.env['hr.payroll.structure.type'].search([('name', '=', row[0].value)], limit=1) @@ -72,17 +60,54 @@ class YuthonPayrollWizard(models.Model): }) def yuthon_payroll_lc_data(self, row): - type_id = self.env['hr.payroll.structure.type'].search([('name', '=', row[0].value)], limit=1) - base_salary = row[1].value - position_subsidy = row[2].value - position_salary = row[3].value - advance_performance_bonuses = row[4].value - payroll_id = self.env['hr.payroll.structure'].search( - [('type_id', '=', type_id.id)]) - if payroll_id: - payroll_id.write({ - 'base_salary': base_salary, - 'position_salary': position_salary, - 'position_subsidy': position_subsidy, - 'advance_performance_bonuses': advance_performance_bonuses, - }) \ No newline at end of file + # 1. 获取传入的行数据 + type_name = row[0].value + base_salary = float(row[1].value) if row[1].value else 0.0 + rank_salary = float(row[2].value) if row[2].value else 0.0 + advance_performance_bonuses = float(row[3].value) if row[3].value else 0.0 + + # 2. 查询薪资结构类型 + type_id = self.env['hr.payroll.structure.type'].search([('name', '=', type_name)], limit=1) + if not type_id: + # 可选:添加日志提示类型不存在 + self.env.user.notify_warning(message=f"未找到薪资结构类型:{type_name}") + return + + # 3. 查找“天河粮储”公司的任意hr.payroll.structure记录 + # 先找到公司(根据公司名称搜索,可替换为公司编码更精准) + target_company = self.env['res.company'].search([('name', '=', '天河粮储')], limit=1) + if not target_company: + self.env.user.notify_warning(message="未找到名称为「天河粮储」的公司") + return + + source_structure = self.env['hr.payroll.structure'].search( + [('company_id.name', '=', target_company.id)], + limit=1 # 取任意一条 + ) + copied_salary_rules = [] + source_salary_rules = source_structure.rule_ids + for rule in source_salary_rules: + rule_vals = rule.copy_data()[0] + for field in ['id', 'create_uid', 'create_date', 'write_uid', 'write_date']: + rule_vals.pop(field, None) + copied_salary_rules.append((0, 0, rule_vals)) + + new_structure = self.env['hr.payroll.structure'].create({ + 'name': type_name, + 'type_id': type_id.id, + 'company_id': target_company.id, + 'rule_ids': copied_salary_rules, + }) + salary_rule_mapping = { + '基本工资': base_salary, + '职级工资': rank_salary, + '预发绩效奖金': advance_performance_bonuses + } + for rule_name, target_value in salary_rule_mapping.items(): + target_rule = new_structure.rule_ids.search( + [('name', '=', rule_name), ('structure_id', '=', new_structure.id)], + limit=1 + ) + if target_rule: + target_rule.write({'amount_fix': target_value}) + From e4eb80888f42d1b09d2ca013ecd5091e65aec038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Sat, 20 Dec 2025 19:12:56 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- soong_workflow/views/soong_workflow_record.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soong_workflow/views/soong_workflow_record.xml b/soong_workflow/views/soong_workflow_record.xml index ba7dc3a8b..d672ed6d4 100644 --- a/soong_workflow/views/soong_workflow_record.xml +++ b/soong_workflow/views/soong_workflow_record.xml @@ -244,7 +244,7 @@ ir.actions.act_window soong.workflow.record tree,form,search - {'search_default_group_by_group_soong_state': True,} + {'search_default_group_by_group_soong_state': True, 'search_default_group_by_group_by_workflow_id': True} [('reclaim_state', '=', False)] From 9b6074e0eb5dc71dfd1d39987ab43a1289c2754e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Sat, 20 Dec 2025 19:36:33 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- soong_workflow/views/soong_workflow_record.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/soong_workflow/views/soong_workflow_record.xml b/soong_workflow/views/soong_workflow_record.xml index d672ed6d4..61edd8feb 100644 --- a/soong_workflow/views/soong_workflow_record.xml +++ b/soong_workflow/views/soong_workflow_record.xml @@ -244,7 +244,7 @@ ir.actions.act_window soong.workflow.record tree,form,search - {'search_default_group_by_group_soong_state': True, 'search_default_group_by_group_by_workflow_id': True} + {'search_default_group_by_group_soong_state': True, 'search_default_group_by_workflow_id': True} [('reclaim_state', '=', False)] From 31c53f67325a9f0fbdfffb0d6888d7937269d467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=B9=8F=E5=AE=87?= <9664676+pengyuthon@user.noreply.gitee.com> Date: Sat, 20 Dec 2025 19:52:13 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../models/property_lease_contract.py | 11 ++++++++++- .../views/property_lease_contract.xml | 1 - 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/yuthon_property_approval/models/property_lease_contract.py b/yuthon_property_approval/models/property_lease_contract.py index 0b6853224..1beebb00c 100644 --- a/yuthon_property_approval/models/property_lease_contract.py +++ b/yuthon_property_approval/models/property_lease_contract.py @@ -614,7 +614,16 @@ class PropertyLeaseContract(models.Model): contract_ids = self.env["property.lease.contract"].browse( self._context.get('active_ids', self._context.get('active_id'))) for rec in contract_ids: - rec.onchange_lease_ids() + if rec.electricity_fee_ids: + for line in rec.electricity_fee_ids: + line.is_fix_loss = 'yes' + if line.unit_price > 0: + line.is_fix_price = 'yes' + if rec.water_fee_ids: + for line in rec.water_fee_ids: + line.is_fix_loss = 'yes' + if line.unit_price > 0: + line.is_fix_price = 'yes' def update_admin_id(self): contract_ids = self.env["property.lease.contract"].browse( diff --git a/yuthon_property_approval/views/property_lease_contract.xml b/yuthon_property_approval/views/property_lease_contract.xml index b3c1a8ad7..03745c446 100644 --- a/yuthon_property_approval/views/property_lease_contract.xml +++ b/yuthon_property_approval/views/property_lease_contract.xml @@ -839,7 +839,6 @@ -