Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
pengyuthon
2025-12-22 13:07:10 +08:00
45 changed files with 739 additions and 175 deletions
+11 -8
View File
@@ -6,7 +6,7 @@
- 快捷筛选(quick_filter)
- 自选时间筛选(select_date_filter)(待移植)
- 字段搜索(field)(待移植)
- 字段搜索(field)(已移植)
通过这些筛选标签,用户可以更灵活地对列表视图中的数据进行筛选。
@@ -20,9 +20,9 @@
#### 支持的属性:
| 属性名 | 类型 | 说明 | 是否可选 |
| ------- | ------ | ---------- | ---- |
| `class` | String | 自定义 CSS 类名 | ✅ |
| 属性名 | 类型 | 说明 | 是否可选 |
| ------- | ------ |-------------------------------------------------------------------| ---- |
| `class` | String | 自定义 CSS 类名,<br/>可使用class="header-search-inline"使所有筛选在同一行并根据宽度自动换行 | ✅ |
#### 子标签支持:
@@ -56,7 +56,7 @@
| ----------- | ------- | ----------- | ------------ |
| `class` | String | 自定义 CSS 类名 | ✅ |
| `string` | String | 显示的标头名称 | ✅ |
| `invisible` | Boolean | 是否默认隐藏 | ✅ |
| `invisible` | Boolean | 是否默认隐藏(废弃) | ✅ |
| `options` | JSON | 扩展参数,支持以下参数: | ✅ |
| | | `radio`: 是否单选 | |
| | | `no_label`: 不显示标题 | |
@@ -77,16 +77,16 @@
---
### 3. 自选时间筛选 (select_date_filter)
### 3. 自选时间筛选 (select_date_filter)(废弃)
**标签名:** `select_date_filter`
#### 支持的属性:
| 属性名 | 类型 | 说明 | 是否可选 |
| ------------------- | ------- | -------------- | ---- |
| ------------------- | ------- |----------------| ---- |
| `class` | String | 自定义 CSS 类名 | ✅ |
| `invisible` | Boolean | 是否默认隐藏 | ✅ |
| `invisible` | Boolean | 是否默认隐藏(废弃) | ✅ |
| `allow_date_fields` | String | 允许筛选的时间字段,逗号分隔 | ✅ |
#### 示例:
@@ -109,6 +109,9 @@
| `string` | String | 显示的标头名称 | ✅ |
| `class` | String | 自定义 CSS | ✅ |
| `invisible` | Boolean | 是否默认隐藏 | ✅ |
| `options` | JSON | 扩展参数,支持以下参数: | ✅ |
| | | `radio`: 是否单选 | |
| | | `no_label`: 不显示标题 | |
#### 示例:
@@ -0,0 +1,33 @@
/** @odoo-module **/
//
// import {patch} from "@web/core/utils/patch";
// import * as backend_utils from "@web/legacy/backend_utils";
//
// const originalSearchModelStateToLegacy = backend_utils.searchModelStateToLegacy;
// const originalGetGlobalState = backend_utils.getGlobalState;
//
// patch(backend_utils, "header_search_panel.backend_utils", {
// searchModelStateToLegacy(state) {
// // 原始state
// const originalState = JSON.parse(state);
// // 获取原始state
// let legacyState = JSON.parse(originalSearchModelStateToLegacy(state));
// if (originalState.headerSearchInfo) {
// legacyState.HeaderSearchInfo = originalState.headerSearchInfo;
// }
// return JSON.stringify(legacyState)
// },
//
// getGlobalState(legacyControllerState) {
// // 原始search model
// const originalSearchModel = JSON.parse(legacyControllerState.searchModel)
// let globalState = originalGetGlobalState(legacyControllerState);
// let searchModel = JSON.parse(globalState?.searchModel || "{}");
// if (originalSearchModel.HeaderSearchInfo) {
// searchModel.headerSearchInfo = originalSearchModel.HeaderSearchInfo;
// globalState.searchModel = JSON.stringify(searchModel)
// }
// console.log('headerSearchInfo: getGlobalState')
// return globalState
// },
// })
@@ -0,0 +1,54 @@
/** @odoo-module **/
import { Component } from "@odoo/owl";
import { Domain } from "@web/core/domain";
import { useService } from "@web/core/utils/hooks";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { standardFieldFilterProps } from "../standard_field_filter_props.js"
export class CharFilter extends Component {
setup() {
super.setup();
this.rpc = useService("rpc");
this.activeValues = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.activeValues || undefined;
}
/**
* 设置筛选字段的值
*/
setActiveValues(ev) {
this.activeValues = ev.target.value;
}
/**
* 获取筛选字段的domain
*/
get domain() {
return this.activeValues ? [[this.props.name, "ilike", this.activeValues]]: [];
}
/**
* 点击清空筛选时触发
*/
resetFilter() {
// 清空筛选字段的值
this.activeValues = undefined;
this.props.fieldOnchange({fieldDomainList: this.domain, activeValues: this.activeValues});
}
getDomain() {
return new Domain(this.props.domain).toList({});
}
async onInputChange(ev) {
this.setActiveValues(ev);
this.props.fieldOnchange({fieldDomainList: this.domain, activeValues: this.activeValues});
}
}
CharFilter.template = "header_search_panel.CharFilter";
CharFilter.components = {};
CharFilter.props = {
...standardFieldFilterProps,
};
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="header_search_panel.CharFilter" owl="1">
<div>
<input class="o_input"
t-att-id="props.id"
type="text"
t-ref="input"
t-att-value="activeValues"
t-on-input="onInputChange"/>
</div>
</t>
</templates>
@@ -1,8 +1,9 @@
/** @odoo-module **/
import {Component} from "@odoo/owl";
import {Domain} from "@web/core/domain";
import {standardFieldFilterProps} from "../standard_field_filter_props.js"
import { Component } from "@odoo/owl";
import { DateTimeInput } from "@web/core/datetime/datetime_input";
import { deserializeDate } from "@web/core/l10n/dates";
import { standardFieldFilterProps } from "../standard_field_filter_props.js"
export class DateFilter extends Component {
setup() {
@@ -13,13 +14,21 @@ export class DateFilter extends Component {
this.endDate = this.env.searchModel.headerSearchInfo.FieldsFilter[this.props.id]?.endDate;
}
get dateFrom() {
return this.startDate ? deserializeDate(this.startDate): undefined;
}
get dateTo() {
return this.endDate ? deserializeDate(this.endDate): undefined;
}
// 设置筛选字段的值
setActiveValues(ev) {
const value = ev.target.value;
if (ev.target.id === this.props.id + "_start_date") {
setActiveValues(dateValue, type) {
const value = dateValue ? (this.props.type === "datetime" ? dateValue.toFormat("yyyy-MM-dd HH:mm:ss") : dateValue.toFormat("yyyy-MM-dd")) : undefined;
if (type === "startDate") {
this.startDate = value
}
if (ev.target.id === this.props.id + "_end_date") {
if (type === "endDate") {
this.endDate = value
}
}
@@ -41,9 +50,14 @@ export class DateFilter extends Component {
return domain
}
// 点击日期选择器时触发
dateOnchange(ev) {
this.setActiveValues(ev);
startDateOnchange (startDate) {
this.setActiveValues(startDate, "startDate");
this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate});
}
endDateOnchange (endDate) {
this.setActiveValues(endDate, "endDate");
this.props.fieldOnchange({fieldDomainList: this.domain, startDate: this.startDate, endDate: this.endDate});
}
@@ -67,6 +81,9 @@ export class DateFilter extends Component {
}
DateFilter.template = "header_search_panel.DateFilter";
DateFilter.components = {
DateTimeInput,
};
DateFilter.props = {
...standardFieldFilterProps,
};
@@ -5,26 +5,33 @@
<t t-name="header_search_panel.DateFilter" owl="1">
<!-- 选择字段选择项 -->
<div class="date-filter">
<input
type="date"
t-attf-id="{{ props.id + '_start_date' }}"
name="start_date"
t-att-value="this.startDate"
t-on-change="dateOnchange"/>
<input
type="date"
t-attf-id="{{ props.id + '_end_date' }}"
name="end_date"
t-att-value="this.endDate"
t-on-change="dateOnchange"/>
<DateTimeInput value="dateFrom"
type="props.type"
onChange.bind="startDateOnchange"/>
<i class="oi oi-arrow-right"/>
<DateTimeInput value="dateTo"
type="props.type"
onChange.bind="endDateOnchange"/>
<!-- <input -->
<!-- t-att-type="props.type" -->
<!-- t-attf-id="{{ props.id + '_start_date' }}" -->
<!-- name="start_date" -->
<!-- t-att-value="this.startDate" -->
<!-- t-on-change="dateOnchange"/> -->
<!-- <input -->
<!-- t-att-type="props.type" -->
<!-- t-attf-id="{{ props.id + '_end_date' }}" -->
<!-- name="end_date" -->
<!-- t-att-value="this.endDate" -->
<!-- t-on-change="dateOnchange"/> -->
</div>
<div class="fast-date-filter">
<a href="#" id="one-month" t-on-click="() => this.setDateRange(1)">近1个月</a>
<a href="#" id="three-months" t-on-click="() => this.setDateRange(3)">近3个月</a>
<a href="#" id="half-year" t-on-click="() => this.setDateRange(6)">近半年</a>
<a href="#" id="one-year" t-on-click="() => this.setDateRange(12)">近1年</a>
<a href="#" id="two-years" t-on-click="() => this.setDateRange(24)">近2年</a>
</div>
<!-- <div class="fast-date-filter"> -->
<!-- <a href="#" id="one-month" t-on-click="() => this.setDateRange(1)">近1个月</a> -->
<!-- <a href="#" id="three-months" t-on-click="() => this.setDateRange(3)">近3个月</a> -->
<!-- <a href="#" id="half-year" t-on-click="() => this.setDateRange(6)">近半年</a> -->
<!-- <a href="#" id="one-year" t-on-click="() => this.setDateRange(12)">近1年</a> -->
<!-- <a href="#" id="two-years" t-on-click="() => this.setDateRange(24)">近2年</a> -->
<!-- </div> -->
</t>
</templates>
@@ -8,6 +8,7 @@ import { BooleanFilter } from "./boolean_filter/boolean_filter";
import { DateFilter } from "./date_filter/date_filter";
import { Many2oneFilter } from "./many2one_filter/many2one_filter";
import { X2ManyFilterFilter } from "./x2many_filter/x2many_filter";
import { CharFilter } from "./char_filter/char_filter";
const fieldRegistry = registry.category("fields");
@@ -41,6 +42,9 @@ export class FieldsFilter extends Component {
case "one2many":
this.addX2ManyFieldDependencies(fieldInfo)
break;
case "char":
this.addCharFieldDependencies(fieldInfo)
break;
}
return fieldInfo;
}
@@ -65,6 +69,9 @@ export class FieldsFilter extends Component {
case "many2many":
case "one2many":
return X2ManyFilterFilter;
case "char":
return CharFilter;
}
}
@@ -128,6 +135,7 @@ export class FieldsFilter extends Component {
domain: field.fieldDomain || "[]",
fieldString: field.fieldString,
searchLimit: field.fieldOptions?.search_limit || 10,
placeholder: field.placeholder,
}
this._addFieldBaseProps(field.props, field);
}
@@ -144,6 +152,13 @@ export class FieldsFilter extends Component {
this._addFieldBaseProps(field.props, field);
}
addCharFieldDependencies(field) {
field.Component = this._getComponent(field);
// 组件props
field.props = {};
this._addFieldBaseProps(field.props, field);
}
// 筛选字段的onChange事件,将筛选字段的id和domain传给searchModel
fieldFilterOnchange(fieldsFilterId, fieldsFilterInfo) {
if (fieldsFilterInfo?.fieldDomainList){
@@ -104,6 +104,7 @@ export class Many2oneFilter extends Component {
activeActions: {create: false, createEdit: false, write: false},
fieldString: this.props.fieldString,
searchLimit: this.props.searchLimit,
placeholder: this.props.placeholder,
};
}
}
@@ -13,4 +13,6 @@ export const standardFieldFilterProps = {
type: {type: String, required: true},
// 字段变换监听函数
fieldOnchange: { type: Function, required: true },
// 提示
placeholder: {type: String, optional: true},
};
@@ -2,11 +2,11 @@
<templates xml:space="preserve">
<!-- 快速筛选组件 -->
<t t-name="header_search_panel.QuickFilter" owl="1">
<!-- <div t-if="props.showLabel" class="header-filter-label">
<div t-if="props.showLabel" class="header-filter-label">
<span>
<t t-esc="props.string" />
</span>
</div> -->
</div>
<!-- quick filter 内的筛选循环 -->
<div class="header-filter-value">
<div class="selection-options">
@@ -74,10 +74,12 @@
.fast-date-filter {
display: flex;
flex-wrap: wrap;
align-items: center;
}
input[type="date"],
.o_datetime_input,
.fast-date-filter a {
width: auto;
margin: 0.2rem;
padding: 0.1rem 1rem;
border: 0.1rem solid $o-community-color; /* 将边框颜色改为黑色 */
@@ -149,6 +151,14 @@
}
}
}
.header-search-inline {
display: flex;
flex-wrap: wrap;
align-items: start;
gap: 5px;
}
.header_search_toggle {
width: 6rem;
height: 2rem;
@@ -161,4 +171,4 @@
line-height: 2rem;
margin-left: 0.5rem;
}
}
}
@@ -165,6 +165,8 @@ patch(SearchArchParser.prototype, {
const fieldString = node.getAttribute("string");
// 自定义css类
const cssClass = node.getAttribute("class");
// placeholder
const placeholder = node.getAttribute("placeholder");
// 获取字段的options属性,该属性代表字段选项
const fieldOptions = evaluateExpr(node.getAttribute("options") || "{}");
// 获取widget
@@ -173,7 +175,7 @@ patch(SearchArchParser.prototype, {
if (fieldName) {
const field = this.fields[fieldName];
// 如果字段类型为selection或者bool,将字段名和选项添加到headerSearchInfo的fields中
if (!["selection", "boolean", "date", "datetime", "many2one", "one2many", "many2many"].includes(field.type)) {
if (!["selection", "boolean", "date", "datetime", "many2one", "one2many", "many2many", "char"].includes(field.type)) {
return false;
}
const fieldInfo = {
@@ -186,6 +188,7 @@ patch(SearchArchParser.prototype, {
fieldOptions: fieldOptions,
modifiers: JSON.parse(node.getAttribute("modifiers") || "{}"),
class: cssClass,
placeholder: placeholder || undefined,
}
if (["many2one", "many2many"].includes(field.type)) {
fieldInfo.fieldDomain = node.getAttribute("domain");
@@ -237,17 +240,16 @@ patch(SearchArchParser.prototype, {
for (const node of headerSearchNode.children) {
switch (node.tagName) {
case "field":
// // 字段筛选
// this.visitHeaderField(node);
// 字段筛选
this.visitHeaderField(node);
break;
case "quick_filter":
// 快捷筛选
this.visitHeaderQuickFilter(node);
break;
case "select_date_filter":
// // 自选日期筛选
// this.visitHeaderSelectDateFilter(node);
break;
// 自选日期筛选
this.visitHeaderSelectDateFilter(node);
}
}
return false;
@@ -244,7 +244,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">soong.workflow.record</field>
<field name="view_mode">tree,form,search</field>
<field name="context">{'search_default_group_by_group_soong_state': True,}</field>
<field name="context">{'search_default_group_by_group_soong_state': True, 'search_default_group_by_workflow_id': True}</field>
<field name="domain">[('reclaim_state', '=', False)]</field>
<field name="search_view_id" ref="soong_workflow_record_search1"/>
<field name="view_id" ref="soong_workflow_record_tree"/>
+18
View File
@@ -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()
"""提交请假申请,区分年假和其他假期处理"""
+3 -1
View File
@@ -44,6 +44,9 @@
<field name="is_back" invisible="1"/>
<field name="name"/>
<field name="users_id" options="{'no_create': True}"/>
<field name="department_id" invisible="1"/>
<field name="entrusted_agent_id" domain="[('employee_id.department_id', '=', department_id)]" />
<field name="is_entrusted" invisible="not entrusted_agent_id"/>
<field name="days" invisible="1"/>
<field name="employee_id" invisible="1" force_save="1"/>
<field name="text"/>
@@ -239,7 +242,6 @@
</form>
</field>
<group>
<field name="department_id" invisible="1"/>
<field name="user_role_id" invisible="1"/>
<field name="is_issues" invisible="1"/>
<field name="start_date" invisible="1"/>
+11 -10
View File
@@ -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())
@@ -40,6 +40,7 @@
</h1>
</div>
<group string="审批事项">
<field name="is_back" invisible="1"/>
<field name="name" required="1"/>
<field name="company_id" invisible="1"/>
<field name="issue_text"/>
@@ -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):
@@ -10,6 +10,7 @@
<field name="software_processing_id"/>
<field name="report_state"/>
<field name="create_time"/>
<button name="pull_order" string="提交" type="object" class="btn-primary"/>
</tree>
</field>
</record>
@@ -12,6 +12,7 @@
<field name="leader_id"/>
<field name="report_state"/>
<field name="create_time"/>
<button name="pull_order" string="提交" type="object" class="btn-primary"/>
</tree>
</field>
</record>
@@ -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="最大位数")
@@ -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
@@ -37,6 +37,44 @@
</tree>
</field>
</record>
<!-- 押金对账列表-->
<record id="view_property_accounts_receivable_tree1" model="ir.ui.view">
<field name="name">property.accounts.receivable.tree</field>
<field name="model">property.accounts.receivable</field>
<field name="arch" type="xml">
<tree editable="bottom" string="应收账单" decoration-success="is_reconciliation == True" limit="15" delete="false">
<field name="acc_type" string=" "/>
<field name="management_type_id" readonly="0"/>
<field name="due_date" readonly="0" width="300px" required="1"/>
<field name="due_date_end" readonly="0"/>
<field name="payout_date" readonly="0"/>
<field name="original_amount" readonly="0"/>
<field name="discount_amount" readonly="0"/>
<field name="delivered1" readonly="0"/>
<field name="submitted" options='{"fg_color": "red: submitted != false;" }'/>
<field name="number_of_payments"/>
<field name="remark"/>
<field name="multiplier" column_invisible="1"/>
<field name="is_push" optional="hide" readonly="0"/>
<field name="reconciliation_time" optional="hide" readonly="0"/>
<field name="create_time" readonly="0"/>
<field name="last_update_time" optional="hide" readonly="0"/>
<field name="is_invalid" column_invisible="1"/>
<field name="void_date" column_invisible="1"/>
<field name="is_red" column_invisible="1"/>
<field name="is_reconciliation" optional="hide"/>
<field name="difference_amount" readonly="0"/>
<field name="receivable_log" column_invisible="1"/>
<field name="is_suspend" optional="hide"/>
<field name="is_write" column_invisible="1"/>
<field name="bank_id" column_invisible="1"/>
<field name="print_number"/>
<field name="print_date"/>
<field name="discount_date"/>
<field name="has_date"/>
</tree>
</field>
</record>
<record id="all_property_accounts_receivable_contract" model="ir.actions.server">
<field name="name">导入应收初始化数据</field>
<field name="model_id" ref="yuthon_property.model_property_accounts_receivable"/>
@@ -118,6 +118,8 @@
<field name="last_meter" string="上次抄表日期"/>
<field name="last_dosage" string="上次用量"/>
<field name="is_push"/>
<field name="is_fix_loss" column_invisible="1"/>
<field name="is_fix_price" column_invisible="1"/>
<field name="number" column_invisible="1"/>
<field name="sequence" column_invisible="1"/>
<field name="meter_date_month" column_invisible="1"/>
@@ -120,6 +120,8 @@
<field name="last_meter" string="上次抄表日期"/>
<field name="last_dosage" string="上次用量"/>
<field name="is_push"/>
<field name="is_fix_loss" column_invisible="1"/>
<field name="is_fix_price" column_invisible="1"/>
<field name="number" column_invisible="1"/>
<field name="sequence" column_invisible="1"/>
<field name="initial_value" column_invisible="1"/>
@@ -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,
+1 -2
View File
@@ -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()
+1 -1
View File
@@ -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()
+1
View File
@@ -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',
],
@@ -617,6 +617,53 @@ class PropertyAccountsReceivable(models.Model):
actual_coping = fields.Float(string="实际应付", compute='_compute_actual_coping', store=True)
charging_range = fields.Text(string="收费区间")
def verification_wizard(self):
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')
def compute_tenure_type(self):
for record in self:
@@ -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(
@@ -2421,15 +2430,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 +2451,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'
@@ -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),
@@ -122,4 +122,6 @@ 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
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
access_deposit_reconciliation_line,deposit_reconciliation_line,model_deposit_reconciliation_line,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
122 access_property_deposit_wizard property_deposit_wizard model_property_deposit_wizard base.group_user 1 1 1 1
123 access_deposit_reconciliation_line deposit_reconciliation_line model_deposit_reconciliation_line base.group_user 1 1 1 1
124
125
126
127
@@ -408,6 +408,35 @@
<field name="this_month_table" column_invisible="1"/>
<field name="ratio" column_invisible="1"/>
<field name="billable_quantity" column_invisible="1"/>
<!-- <field name="company_id1"/>-->
<field name="contract_id" string="合同编号"/>
</xpath>
<xpath expr="//field[@name='receivable_log']" position="after">
<field name="penalty"/>
<field name="penalty_date"/>
</xpath>
</field>
</record>
<record id="inherit_property_accounts_receivable_form_tree" model="ir.ui.view">
<field name="name">property.accounts.receivable.tree</field>
<field name="model">property.accounts.receivable</field>
<field name="inherit_id" ref="yuthon_property.view_property_accounts_receivable_tree1"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='has_date']" position="before">
<button name="verification_wizard" string="核销" type="object" class="btn btn-primary"/>
</xpath>
<xpath expr="//field[@name='remark']" position="after">
<field name="charging_range"/>
<field name="is_penalty" column_invisible="1"/>
<field name="property_name" width="350px"/>
<field name="property_ids" options="{'no_create': True}" widget="many2many_tags" column_invisible="1"/>
<field name="lessee_id" options="{'no_create': True}" readonly="1"/>
<field name="title_deed" readonly="1"/>
<field name="company_id" column_invisible="1"/>
<field name="last_month_table" column_invisible="1"/>
<field name="this_month_table" column_invisible="1"/>
<field name="ratio" column_invisible="1"/>
<field name="billable_quantity" column_invisible="1"/>
<!-- <field name="company_id1"/>-->
<field name="contract_id" string="合同编号"/>
</xpath>
@@ -467,6 +496,15 @@
<field name="search_view_id" ref="view_property_accounts_receivable_search"/>
<field name="view_id" ref="yuthon_property.view_property_accounts_receivable_tree"/>
</record>
<record id="accounts_receivable_deposit_action" model="ir.actions.act_window">
<field name="name">押金列表</field>
<field name="res_model">property.accounts.receivable</field>
<field name="view_mode">tree</field>
<field name="context">{'search_default_group_by_contract_id': True}</field>
<field name="domain">[('management_type_id.name','=', '押金')]</field>
<field name="search_view_id" ref="view_property_accounts_receivable_search"/>
<field name="view_id" ref="yuthon_property.view_property_accounts_receivable_tree1"/>
</record>
<!-- 合同应收报表 -->
<record id="view_property_accounts_receivable_report_tree" model="ir.ui.view">
<field name="name">property.accounts.receivable.tree.form</field>
@@ -279,8 +279,9 @@
<field name="unit_number"/>
<field name="property_meter_water_id" options="{'no_open': True}"/>
<field name="rate"/>
<field name="line_loss"/>
<field name="unit_price"/>
<field name="is_fix_loss"/>
<field name="is_fix_price"/>
<field name="unit_price" readonly="is_fix_price == 'no'"/>
<field name="start_date"/>
<field name="end_date"/>
</tree>
@@ -294,8 +295,9 @@
<field name="unit_number"/>
<field name="property_meter_archives_id" options="{'no_open': True}"/>
<field name="rate"/>
<field name="line_loss"/>
<field name="unit_price"/>
<field name="is_fix_loss"/>
<field name="is_fix_price"/>
<field name="unit_price" readonly="is_fix_price == 'no'"/>
<field name="start_date"/>
<field name="end_date"/>
</tree>
@@ -819,8 +821,9 @@
<field name="unit_number"/>
<field name="property_meter_water_id" options="{'no_open': True}"/>
<field name="rate"/>
<field name="line_loss"/>
<field name="unit_price"/>
<field name="is_fix_loss"/>
<field name="is_fix_price"/>
<field name="unit_price" readonly="is_fix_price == 'no'"/>
<field name="start_date"/>
<field name="end_date"/>
</tree>
@@ -833,9 +836,9 @@
<field name="epitope_values_id"/>
<field name="unit_number"/>
<field name="property_meter_archives_id" options="{'no_open': True}"/>
<field name="rate"/>
<field name="line_loss"/>
<field name="unit_price"/>
<field name="is_fix_loss"/>
<field name="is_fix_price"/>
<field name="unit_price" readonly="is_fix_price == 'no'"/>
<field name="start_date"/>
<field name="end_date"/>
</tree>
@@ -19,7 +19,7 @@
<form string="租赁变更审批" create="0">
<sheet>
<widget name="web_ribbon" title="普件" bg_color="text-bg-success"
invisible="urgency_level != 'normal'"/>
invisible="urgency_level != 'normal'"/>
<widget name="web_ribbon" title="急件" bg_color="text-bg-warning"
invisible="urgency_level != 'urgent'"/>
<widget name="web_ribbon" title="特级" bg_color="text-bg-danger"
@@ -41,7 +41,7 @@
<group string="审批事项">
<field name="is_back" invisible="1"/>
<field name="name" required="1"/>
<field name="renewal_type" widget="radio"/>
<field name="renewal_type" widget="radio"/>
<field name="company_id" invisible="1"/>
<field name="department_id" invisible="1"/>
<field name="work_end" invisible="1"/>
@@ -83,34 +83,63 @@
<field name="phone" invisible="renewal_type != 'lease'"/>
</group>
</group>
<group>
<field name="reason" invisible="renewal_type != 'lease'"/>
<field name="effective_date" invisible="renewal_type != 'lease'"/>
<field name="early_reason" invisible="renewal_type != 'early_checkout'"/>
<field name="deposit" invisible="renewal_type != 'early_checkout'"/>
<field name="is_full_refund" invisible="renewal_type != 'early_checkout'"/>
</group>
<group col="4">
<group>
<field name="reason" invisible="renewal_type != 'lease'"/>
<field name="effective_date" invisible="renewal_type != 'lease'"/>
<field name="early_reason" invisible="renewal_type != 'early_checkout'"/>
<field name="deposit" invisible="renewal_type != 'early_checkout'"/>
<field name="is_full_refund" invisible="renewal_type != 'early_checkout'"/>
<field name="refund_amount" invisible="is_full_refund"/>
<field name="balance" invisible="is_full_refund"/>
<field name="remake" invisible="is_full_refund"/>
<field name="early_explain" invisible="renewal_type != 'early_checkout'"/>
<field name="document_ids" widget="preview_many2many"/>
</group>
<group>
<field name="refund_remake" invisible="not refund_amount"/>
</group>
<group/>
<group/>
</group>
<group col="4">
<group>
<field name="balance" invisible="is_full_refund"/>
</group>
<group>
<field name="balance_remake" invisible="not balance"/>
</group>
<group/>
<group/>
</group>
<group col="4">
<group>
<field name="total_receivables" invisible="is_full_refund"/>
</group>
<group>
<field name="total_remake" invisible="not total_receivables"/>
</group>
<group/>
<group/>
</group>
<group>
<field name="early_explain" invisible="renewal_type != 'early_checkout'"/>
<field name="document_ids" widget="preview_many2many"/>
</group>
<notebook>
<page string="应收明细" invisible="renewal_type != 'early_checkout'">
<field name="receivables_ids" string=" ">
<tree editable="bottom">
<field name="renewal_id" column_invisible="1"/>
<field name="receivable_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="original_amount"/>
<field name="reduction_amount"/>
<field name="refund_amount"/>
<field name="is_reconciliation"/>
<field name="void"/>
</tree>
<field name="receivables_ids" string=" ">
<tree editable="bottom">
<field name="renewal_id" column_invisible="1"/>
<field name="receivable_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="original_amount"/>
<field name="reduction_amount"/>
<field name="refund_amount"/>
<field name="is_reconciliation"/>
<field name="void"/>
</tree>
</field>
</page>
</page>
</notebook>
</sheet>
</form>
+2 -1
View File
@@ -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
from . import action_refuse_wizard
from . import property_deposit_wizard
@@ -0,0 +1,68 @@
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 = '押金核销'
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='对账明细')
def confirm(self):
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):
_name = 'deposit.reconciliation.line'
_description = '押金对账明细'
_order = 'due_date desc'
reconciliation_id = fields.Many2one('property.deposit.wizard', string='关联押金对账单弹窗')
receivable_id = fields.Many2one('property.accounts.receivable', string='关联应收账单')
bill_id = fields.Many2one('property.bills.list', string='关联应付账单')
due_date = fields.Date(string='应收日期')
management_type_id = fields.Many2one('yuthon.management.type', string='类型')
payment_type = fields.Selection([('pay', '支付'), ('spending', '支出')], string='收付款类型')
amount = fields.Float(string='金额', store=True)
write_amount = fields.Float(string='核销金额')
remake = fields.Text(string='备注')
@api.onchange('write_amount')
def onchange_remake(self):
for line in self:
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
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="property_deposit_wizard_form" model="ir.ui.view">
<field name="name">property.deposit.wizard.form</field>
<field name="model">property.deposit.wizard</field>
<field name="arch" type="xml">
<form string="押金核销">
<sheet>
<div class="oe_title">
<label for="actual_date"/>
<h1>
<field name="actual_date" style="font-weight: bold; color: #000000;" force_save="1"/>
</h1>
</div>
<group col="3">
<group>
<field name="property_name" readonly="1"/>
</group>
<group>
<field name="lessee_id" readonly="1"/>
</group>
<group>
<field name="contract_id" readonly="1"/>
</group>
</group>
<group>
<field name="deposit"/>
</group>
<field name="reconciliation_line_ids">
<tree editable="bottom">
<field name="reconciliation_id" column_invisible="1"/>
<field name="receivable_id" column_invisible="1"/>
<field name="bill_id" column_invisible="1"/>
<field name="management_type_id"/>
<field name="due_date"/>
<field name="payment_type"/>
<field name="amount"/>
<field name="write_amount"/>
<field name="remake"/>
</tree>
</field>
<footer>
<button type="object" name="confirm" string="确认" class="oe_highlight"/>
<button special="cancel" string="取消" class="btn-secondary"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>
@@ -12,7 +12,7 @@
<field name="actual_date" style="font-weight: bold; color: #000000;" readonly="1" force_save="1"/>
</h1>
</div>
<group col="4">
<group col="3">
<group>
<field name="lessee_ids" widget="many2many_tags" readonly="1"/>
</group>
@@ -22,7 +22,6 @@
<group>
<field name="remake" readonly="1"/>
</group>
<group/>
</group>
<group>
<field name="bank_id" invisible="1"/>
@@ -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):
"""定时任务:根据日志记录的生效时间更新主模型字段值"""
@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record model="ir.rule" id="hr_payroll_structure_type_rule">
<field name="name">职级类型多公司</field>
<field name="model_id" ref="model_hr_payroll_structure_type"/>
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
</record>
<record model="ir.rule" id="salary_payroll_distribution_rule">
<field name="name">薪资方法多公司</field>
<field name="model_id" ref="model_salary_payroll_distribution"/>
@@ -100,6 +100,7 @@
<field name="arch" type="xml">
<search string="职级查询">
<field name="name"/>
<filter name="is_enable" string="启用" domain="[('is_enable', '=', True)]"/>
<group expand="0">
<filter name="group_date" string="查询年月" context="{'group_by': 'create_time'}"/>
<filter name="group_lv_name" string="级别" context="{'group_by': 'lv_name'}"/>
@@ -112,7 +113,7 @@
<field name="name">职级</field>
<field name="res_model">hr.payroll.structure</field>
<field name="view_mode">tree,form,search</field>
<!-- <field name="context">{'group_by_no_leaf':1,'group_by':[]}</field>-->
<field name="context">{'search_default_is_enable':1}</field>
</record>
<record id="all_paroll_write_salary" model="ir.actions.server">
<field name="name">批量更新职级类型</field>
+52 -27
View File
@@ -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,
})
# 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})
+8 -2
View File
@@ -874,7 +874,7 @@
sequence="50"/>
<menuitem id="yuthon_accounts_receivable_menu"
sequence="1"
name="应收账单(明细)"
name="应收账单"
parent="yuthon_financial_management_menu"
action="yuthon_property_approval.property_accounts_receivable_action"/>
<!-- <menuitem-->
@@ -889,8 +889,14 @@
name="应付账单"
action="yuthon_property_approval.view_property_bills_list_action"
sequence="4"/>
<menuitem
id="accounts_receivable_deposit_menu"
parent="yuthon_financial_management_menu"
name="押金列表"
action="yuthon_property_approval.accounts_receivable_deposit_action"
sequence="10"/>
<menuitem id="yuthon_subject_form_menu"
sequence="5"
sequence="15"
name="预算科目表"
parent="yuthon_financial_management_menu"
action="yuthon_property.view_property_subject_action"/>