Odoo表单布局模块

This commit is contained in:
夜未央
2025-09-17 23:26:53 +08:00
parent ff2089aaba
commit d25d898bf3
20 changed files with 783 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
from . import models
+27
View File
@@ -0,0 +1,27 @@
{
"name": "房地产管理系统", # 模块名称
"summary": "管理房地产属性、优惠和标签", # 模块摘要
"description": "用于管理房地产属性、报价和标签的模块。", # 模块描述
"version": "1.0", # 版本号
"category": "MyAddons/Estate", # 模块分类
"data": [
"security/ir.model.access.csv",
"views/estate_property_offer_views.xml", # 视图文件
"views/estate_property_tag_views.xml", # 视图文件
"views/estate_property_type_views.xml", # 视图文件
"views/estate_property_views.xml", # 视图文件
"views/res_users_views.xml", # 用户视图文件
"views/report_actions.xml", # 报表操作文件
"views/report_template.xml", # 报表模板文件
"views/estate_menus.xml", # 菜单文件
], # 数据文件列表
"assets": {
"web.assets_backend": [
"estate/static/src/scss/estate.scss",
]
},
"depends": ["base", "mail"], # 依赖的模块列表
"license": "LGPL-3", # 许可证类型
"installable": True, # 是否可安装
"application": True, # 是否为应用程序
}
+5
View File
@@ -0,0 +1,5 @@
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import res_users
+181
View File
@@ -0,0 +1,181 @@
from odoo import models, fields, api
from datetime import timedelta
from odoo.exceptions import UserError, ValidationError
# 导入异常类
class EstateProperty(models.Model):
_name = "estate.property"
_description = "房地产属性"
# 继承邮件线程和活动混合类
_inherit = ["mail.thread", "mail.activity.mixin"]
# 默认按ID降序排列
_order = "id desc"
name = fields.Char(string="名称", required=True)
description = fields.Text(string="描述")
postcode = fields.Char(string="邮政编码")
# 默认日期为三个月后
date_availability = fields.Date(
string="可用日期",
copy=False,
default=lambda self: fields.Date.today() + timedelta(days=90),
)
expected_price = fields.Float(string="预期价格", required=True)
selling_price = fields.Float(string="销售价格", copy=False)
bedrooms = fields.Integer(string="卧室数量", default=2)
living_area = fields.Integer(string="居住面积")
facades = fields.Integer(string="立面")
garage = fields.Boolean(string="是否有车库")
garden = fields.Boolean(string="是否有花园")
garden_area = fields.Integer(string="花园面积")
garden_orientation = fields.Selection(
[("north", "北"), ("south", "南"), ("east", "东"), ("west", "西")],
string="花园朝向",
)
# 默认货币为公司货币
currency_id = fields.Many2one(
"res.currency",
string="货币",
required=True,
default=lambda self: self.env.ref("base.CNY").id,
)
active = fields.Boolean(string="是否归档", default=True)
# 房屋类型
state = fields.Selection(
[
("new", "新"),
("offer_received", "已收到报价"),
("offer_accepted", "已接受报价"),
("sold", "已售出"),
("canceled", "已取消"),
],
string="状态",
default="new",
required=True,
copy=False,
)
versionflog = fields.Integer(string="换版", default=1)
# 关联房屋类型模型
property_type_id = fields.Many2one(
"estate.property.type", string="房屋类型", required=True
)
# 买家
buyer_id = fields.Many2one("res.partner", string="买家", copy=False)
# 关联联系人模型卖家
seller_id = fields.Many2one(
"res.users", string="卖家", required=True, default=lambda self: self.env.user
)
# 房屋标签
tag_ids = fields.Many2many("estate.property.tag", string="房屋标签")
# 房屋报价
offer_ids = fields.One2many(
"estate.property.offer", "property_id", string="房屋报价"
)
# 房屋总面积
total_area = fields.Float(
string="总面积", compute="_compute_total_area", store=True
)
# 最佳报价
best_price = fields.Float(
string="最佳报价", compute="_compute_best_price", store=True
)
# 房产预期价格必须严格为正数
_sql_constraints = [
("check_expected_price", "CHECK(expected_price > 0)", "预期价格必须为正数"),
("check_selling_price", "CHECK(selling_price > 0)", "销售价格必须为正数"),
("check_bedrooms", "CHECK(bedrooms > 0)", "卧室数量必须为正数"),
("check_living_area", "CHECK(living_area > 0)", "居住面积必须为正数"),
("check_facades", "CHECK(facades > 0)", "立面数量必须为正数"),
]
# 无法接受低于预期价格 90% 的报价
@api.constrains("selling_price", "expected_price") # 添加约束
def _check_selling_price(self):
for record in self:
if record.selling_price < record.expected_price * 0.9:
raise ValidationError("销售价格不能低于预期价格的90%")
@api.depends("living_area", "garden_area") # 计算字段
def _compute_total_area(self):
"""计算房屋总面积"""
for record in self:
record.total_area = record.living_area + record.garden_area
@api.depends("offer_ids.price") # 计算字段
def _compute_best_price(self):
"""计算最佳报价"""
for record in self:
best_price = 0.0
if record.offer_ids:
best_price = max(record.offer_ids.mapped("price"))
record.best_price = best_price
def action_sold(self):
"""标记为已售(需先接受报价)"""
if self.state != "offer_accepted":
raise UserError("必须接受报价后才能标记已售")
return self.write({"state": "sold"})
def action_cancel(self):
"""取消交易"""
if self.state == "sold":
raise UserError("已售房产不可取消")
return self.write({"state": "canceled"})
# 如果有花园,设置默认花园面积为50,朝向为南,若没有花园,则设置花园面积为0,朝向为无
@api.onchange("garden")
def _onchange_garden(self):
if self.garden:
self.garden_area = 50
self.garden_orientation = "south"
else:
self.garden_area = 0
self.garden_orientation = False
# 若取消选择车库则弹出警告
@api.onchange("garage")
def _onchange_garage(self):
if not self.garage == False:
return {"warning": {"title": "警告", "message": "确定取消车库吗?"}}
# •如果房产的状态不是‘新建’或‘已取消’,则阻止删除房产
@api.ondelete(at_uninstall=True)
def _unlink_except_new_canceled(self):
"""删除房产"""
for record in self:
if record.state not in ["new", "canceled"]:
raise UserError("只能删除新建或已取消的房产")
# 设置版本日志
def setversionflog(self):
# 空函数,不做任何操作
pass
def write(self, vals):
vals["create_uid"] = self.create_uid
return super(EstateProperty, self).write(vals)
# 定义按钮
def action_set_offer_received(self):
"""设置为已收到报价"""
return self.write({"state": "offer_received"})
def action_set_offer_accepted(self):
"""设置为已接受报价"""
return self.write({"state": "offer_accepted"})
def action_set_sold(self):
"""设置为已售出"""
# 将状态设置为已售出
return self.write({"state": "sold"})
def do_reject_new(self):
"""拒绝新建报价"""
# 将状态设置为"new"
return self.write({"state": "new"})
def do_reject_offer(self):
"""拒绝报价"""
return self.write({"state": "offer_received"})
+77
View File
@@ -0,0 +1,77 @@
from odoo import models,fields,api;# 导入Odoo模型、字段和API模块
from datetime import timedelta;# 导入时间模块
from odoo.exceptions import UserError;# 导入用户错误模块
from odoo.exceptions import ValidationError;# 导入验证错误模块
# 房屋报价模型
class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer';
_description = '房屋报价'
_order = "price desc";# 默认按价格降序排列
price = fields.Float(string='报价', required=True);
status = fields.Selection([('Accepted', '已接受'),('Refused', '已拒绝')], string='状态',copy=False);
partner_id = fields.Many2one('res.partner', string='客户', required=True);
property_id = fields.Many2one('estate.property', string='房屋', required=True);
validity = fields.Integer(string='有效期', default=7);# 默认有效期为7天
date_deadline = fields.Date(string='截止日期', compute='_compute_date_deadline',inverse='_inverse_validity', store=True);# 计算字段,存储在数据库中
property_type_id = fields.Many2one(related='property_id.property_type_id', string='房屋类型', store=True);# 关联房屋类型模型
# 保价必须为严格正数
_sql_constraints = [
('check_price', 'CHECK(price > 0)', '报价必须为正数')
]
@api.depends('validity',"create_date")# 计算字段
def _compute_date_deadline(self):
for record in self:
if record.create_date:
# 计算截止日期
record.date_deadline = record.create_date + timedelta(days=record.validity);
else:
# 如果创建日期为空,则为当前日期
record.date_deadline = fields.Date.today()+ timedelta(days=record.validity);
# 反向计算有效期,inverse方法
def _inverse_validity(self):
for record in self:
if record.date_deadline:
if record.create_date:
validity = (record.date_deadline - record.create_date.date()).days;
# 如果有效期小于0,则抛出异常
if validity < 0:
raise UserError("有效期不能小于0天")
record.validity = validity;
else:
validity = (record.date_deadline - fields.Datetime.now().date()).days;
# 如果有效期小于0,则抛出异常
if validity < 0:
raise UserError("有效期不能小于0天")
record.validity = validity;
# 接受报价
def action_accept(self):
# 将房屋状态改为已接受报价
self.property_id.state = 'offer_accepted';
# 将房屋的销售价格设置为报价价格
self.property_id.selling_price = self.price;
# 将买家设置为报价的客户
self.property_id.buyer_id = self.partner_id.id;
# 将报价的状态设置为已接受
self.status = 'Accepted';
# 拒绝报价
def action_refuse(self):
# 将房屋状态改为已收到报价
self.property_id.state = 'offer_received';
# 将报价的状态设置为已拒绝
self.status = 'Refused';
# 创建报价时,将房产状态设置为‘收到报价’。如果用户尝试创建价格低于现有报价的报价,则引发错误。
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
# 检查房产状态是否为‘收到报价’
property_id = vals.get('property_id')
property_record = self.env['estate.property'].browse(property_id);
property_record.state = 'offer_received';
price = vals.get('price')
if price<property_record.best_price:
raise UserError("报价必须高于现有报价")
return super(EstatePropertyOffer, self).create(vals_list);
+13
View File
@@ -0,0 +1,13 @@
from odoo import models,fields;
class EstatePropertyTag(models.Model):
_name = 'estate.property.tag';
_description = '房屋标签'
_order = "name";# 默认按名称升序排列
name = fields.Char(string='房屋标签', required=True);
color = fields.Integer(string='颜色',default=0);# 颜色字段
# 房屋标签必须唯一
_sql_constraints = [
('check_name', 'UNIQUE(name)', '房屋标签必须唯一')
]
+20
View File
@@ -0,0 +1,20 @@
from odoo import models,fields,api;
class EstatePropertyType(models.Model):
_name = 'estate.property.type';
_description = '房屋类型'
_order = 'sequence';# 排序字段
name = fields.Char(string='房屋类型', required=True);
property_ids = fields.One2many('estate.property', 'property_type_id', string='房屋');# 关联房屋模型
sequence= fields.Integer(string='排序', default=1);# 排序字段
offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='房屋报价');# 关联房屋报价模型
offer_count = fields.Integer(string='报价数量', compute='_compute_offer_count', store=True);# 计算字段,存储在数据库中
# 房屋类型必须唯一
_sql_constraints = [
('check_name', 'UNIQUE(name)', '房屋类型必须唯一')
]
# 计算报价数量
@api.depends('offer_ids')
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids);# 计算报价数量
+5
View File
@@ -0,0 +1,5 @@
from odoo import models, fields, api;# 导入模型、字段和api模块
class ResUsers(models.Model):
_inherit = 'res.users'
property_ids = fields.One2many('estate.property', 'seller_id', string='我的房产',domain="[('state', '!=', 'sold')]")# 关联房屋模型,排除已售房产
+5
View File
@@ -0,0 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1
estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1
estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1
estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 estate.access_estate_property access_estate_property estate.model_estate_property base.group_user 1 1 1 1
3 estate.access_estate_property_type access_estate_property_type estate.model_estate_property_type base.group_user 1 1 1 1
4 estate.access_estate_property_tag access_estate_property_tag estate.model_estate_property_tag base.group_user 1 1 1 1
5 estate.access_estate_property_offer access_estate_property_offer estate.model_estate_property_offer base.group_user 1 1 1 1
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg
class="icon"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M618.2 340.5H220.9l181.6-190.8 134.2 144.1c8.7 9.3 23.4 9.9 32.8 1.2 9.4-8.7 9.9-23.4 1.2-32.7l-151-162.2c-4.4-4.7-10.5-7.4-16.9-7.4-6.5 0.5-12.5 2.6-17 7.2L150 347.7c-4.1 4.3-6.4 10-6.4 16v534.8c0 12.8 10.4 23.2 23.2 23.2h451.4c12.8 0 23.2-10.4 23.2-23.2V363.7c0-12.8-10.3-23.2-23.2-23.2zM595 875.3H190V386.8h405v488.5z m262.3-440H699.5c-12.8 0-23.2 10.4-23.2 23.2 0 12.8 10.4 23.2 23.2 23.2H834v393.9H699.5c-12.8 0-23.2 10.4-23.2 23.2 0 12.8 10.4 23.2 23.2 23.2h157.7c12.8 0 23.2-10.4 23.2-23.2V458.4c0.1-12.8-10.3-23.1-23.1-23.1z m-339 21.2h-78.8v78.6h78.8v-78.6z m-172.7 0h-78.8v78.6h78.8v-78.6z m172.7 139.2h-78.8v78.6h78.8v-78.6z m-172.7 0h-78.8v78.6h78.8v-78.6z m172.7 139.1h-78.8v78.6h78.8v-78.6z m-172.7 0h-78.8v78.6h78.8v-78.6z"
fill="currentColor"
/>
</svg>

After

Width:  |  Height:  |  Size: 918 B

+20
View File
@@ -0,0 +1,20 @@
.estate_form .o_box.stat-card {
background: #fff;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s ease-in-out;
}
.estate_form .o_box.stat-card:hover {
transform: translateY(-3px);
}
.estate_form .o_box.stat-card h3 {
font-size: 18px;
font-weight: bold;
margin: 5px 0 0 0;
}
.estate_form .o_box.stat-card span {
font-size: 14px;
color: #888;
}
+13
View File
@@ -0,0 +1,13 @@
<odoo>
<menuitem id="estate_property_menu_root" name="房地产信息" web_icon="estate,static/description/icon.png">
<menuitem id="estate_property_first_level_property" name="房屋信息">
<menuitem id="estate_property_model_menu_action" action="estate_property_model_action" />
</menuitem>
<menuitem id="estate_property_first_level_setting" name="设置">
<menuitem id="estate_property_type_model_menu_action"
action="estate_property_type_model_action" />
<menuitem id="estate_property_tag_model_menu_action"
action="estate_property_tag_model_action" />
</menuitem>
</menuitem>
</odoo>
@@ -0,0 +1,38 @@
<odoo>
<record id="estate_property_offer_model_action" model="ir.actions.act_window">
<field name="name">房屋报价</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">tree</field>
<field name="domain">[('property_type_id', '=', active_id)]</field>
</record>
<!-- 列表视图配置 -->
<record id="estate_property_offer_view_tree" model="ir.ui.view">
<field name="name">estate.property.offer.tree</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<tree editable="bottom" string="房屋报价"
decoration-danger="status == 'Refused'"
decoration-success="status == 'Accepted'"
>
<field name="price" />
<field name="status" />
<field name="validity" />
<field name="date_deadline" />
<field name="partner_id" />
<field name="property_id" />
<button name="action_accept" string="接受报价" type="object" icon="fa-check" invisible="status" />
<button name="action_refuse" string="拒绝报价" type="object" icon="fa-times" invisible="status" />
</tree>
</field>
</record>
<!-- 搜索视图配置 -->
<record id="estate_property_tag_search_view" model="ir.ui.view">
<field name="name">estate.property.tag.search</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<search string="搜索">
<field name="name" />
</search>
</field>
</record>
</odoo>
@@ -0,0 +1,28 @@
<odoo>
<record id="estate_property_tag_model_action" model="ir.actions.act_window">
<field name="name">房屋标签</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">tree</field>
</record>
<!-- 列表视图配置 -->
<record id="estate_property_tag_view_tree" model="ir.ui.view">
<field name="name">estate.property.tag.tree</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name"/>
<field name="color"/>
</tree>
</field>
</record>
<!-- 搜索视图配置 -->
<record id="estate_property_tag_search_view" model="ir.ui.view">
<field name="name">estate.property.tag.search</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<search string="搜索">
<field name="name" />
</search>
</field>
</record>
</odoo>
@@ -0,0 +1,80 @@
<odoo>
<record id="estate_property_type_model_action" model="ir.actions.act_window">
<field name="name">房屋类型</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">tree,form</field>
</record>
<!-- 列表视图配置 -->
<record id="estate_property_type_view_tree" model="ir.ui.view">
<field name="name">estate.property.type.tree</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<tree string="房屋类型">
<field name="sequence" widget="handle" />
<field name="name" string="房屋类型" />
</tree>
</field>
</record>
<!-- 表单视图配置 -->
<record id="estate_property_type_form_view" model="ir.ui.view">
<field name="name">estate.property.type.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form string="房屋类型">
<!-- 头部状态栏 -->
<header>
<!-- 按钮放在最右边 -->
<button
class="oe_stat_button "
name="%(estate_property_offer_model_action)d"
type="action"
icon="fa-bars"
>
<div class="o_stat_info">
<span class="o_stat_value">
<field name="offer_count" />
</span>
<span class="o_stat_text">
报价数量
</span>
</div>
</button>
</header>
<sheet>
<group>
<h1>
<field name="name" string="房屋类型" />
</h1>
<br/>
<field name="offer_count" />
</group>
<notebook>
<page string="房产">
<field name="property_ids">
<tree>
<field name="name" string="房产名称" />
<field name="expected_price" string="预期价格" />
<field name="state" string="状态" />
<field name="date_availability" string="可用日期" />
</tree>
</field>
</page>
<page string="房屋报价">
<field name="offer_ids"></field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- 搜索视图配置 -->
<record id="estate_property_type_search_view" model="ir.ui.view">
<field name="name">estate.property.type.search</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<search string="搜索">
<field name="name" />
</search>
</field>
</record>
</odoo>
+165
View File
@@ -0,0 +1,165 @@
<odoo>
<record id="estate_property_model_action" model="ir.actions.act_window">
<field name="name">房地产信息</field>
<field name="res_model">estate.property</field>
<field name="view_mode">tree,form,kanban</field>
<field name="context">{}</field>
</record>
<!-- 列表视图配置 -->
<record id="estate_property_view_tree" model="ir.ui.view">
<field name="name">estate.property.tree</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<tree string="房地产列表" decoration-danger="state == 'canceled'" decoration-success="state in ['offer_received', 'offer_accepted']" decoration-bf="state == 'offer_accepted'" decoration-muted="state == 'sold'">
<field name="state" />
<field name="name" string="房产名称" />
<field name="expected_price" string="预期价格" />
<field name="selling_price" string="成交价格" />
<field name="bedrooms" string="卧室数" />
<field name="living_area" string="面积(㎡)" />
<field name="state" string="状态" />
<field name="date_availability" string="可用日期" optional="True" />
</tree>
</field>
</record>
<!-- 表单视图配置 -->
<record id="view_estate_property_form_custom" model="ir.ui.view">
<field name="name">estate.property.form.custom</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="房地产信息" class="estate_form">
<header>
<field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold,canceled" statusbar_colors='{"sold":"success","canceled":"danger"}' class="oe_statusbar_buttons" />
<!-- 标记已售按钮 -->
<button name="action_sold" type="object" string="标记已售" class="oe_highlight" confirm="确认完成交易?" invisible="state in ['sold','canceled']" domain="[('state','=','offer_accepted')]">
<field name="state" invisible="1" />
</button>
<!-- 取消交易按钮 -->
<button name="action_cancel" type="object" string="取消交易" class="oe_unlink" confirm="确认取消该交易?" invisible="state in ['sold','canceled']" domain="[('state','!=','canceled')]">
<field name="state" invisible="1" />
</button>
</header>
<sheet>
<!-- 标题 -->
<div class="oe_title">
<h1 style="font-size:24px; font-weight:700; color:#1E90FF; margin-bottom:12px;">
<field name="name"/>
</h1>
</div>
<div class="o_row" style="margin-top: 20px;">
<!-- 房屋报价 -->
<div class="o_box" style="flex:1; margin-right:10px; padding:15px; border:1px solid #ddd; border-radius:6px;">
<h3 style="border-bottom:2px solid #4CAF50; padding-bottom:6px; margin-bottom:12px; font-weight:600; color:#333;">📑 房屋报价</h3>
<field name="offer_ids" context="{'default_property_id': id}" options="{'no_create': True, 'no_open': True}" readonly='state in ["offer_accepted","sold","canceled"]' />
</div>
</div>
<!-- 两栏布局:基础信息 + 费用信息 -->
<div class="o_row" style="margin-top: 20px; display:flex; align-items:stretch;">
<!-- 基础信息 -->
<div class="o_box" style="flex:1; margin-right:10px; padding:15px; border:1px solid #ddd; border-radius:6px; display:flex; flex-direction:column;">
<h3 style="border-bottom:2px solid #2196F3; padding-bottom:6px; margin-bottom:12px; font-weight:600; color:#333;">📅 基础信息</h3>
<group col="2" style="flex:1;">
<field name="postcode" placeholder="6位邮政编码" />
<field name="date_availability" widget="daterange" options="{'endDateField': 'date_availability'}" />
<field name="property_type_id" options="{'no_create': True, 'no_open': True}" />
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" />
<field name="garage" widget="boolean_toggle" options='{"terminology": {"active": "有车库","inactive": "无车库"}}' />
<field name="active" widget="boolean_toggle" options='{"terminology": {"active": "可用","inactive": "不可用"}}' />
<field name="garden" />
<field name="garden_orientation" widget="selection_badge" options="{'horizontal': True, 'max_items': 4}" invisible="not garden" />
</group>
</div>
<!-- 费用信息 -->
<div class="o_box" style="flex:2; margin-left:10px; padding:15px; border:1px solid #ddd; border-radius:6px; display:flex; flex-direction:column;">
<h3 style="border-bottom:2px solid #FF9800; padding-bottom:6px; margin-bottom:12px; font-weight:600; color:#333;">💰 费用信息</h3>
<group col="2" style="flex:1;">
<field name="bedrooms" default="2" />
<field name="living_area" string="面积(㎡)" />
<field name="garden_area" invisible="not garden" />
<field name="facades" />
<field name="seller_id" string="销售专员" />
<field name="buyer_id" string="客户名称" />
</group>
<!-- 金额卡片展示区 -->
<div class="o_row" style="margin-top:20px; display:flex; flex:0;">
<div class="o_box stat-card" style="flex:1; margin:5px; padding:15px; text-align:center; border-radius:8px; border:1px solid #ddd; background:#f9fafb;">
<span style="font-size:14px; color:#555;">总面积 (㎡)</span>
<h3 style="margin-top:8px; color:#333;">
<field name="total_area"/>
</h3>
</div>
<div class="o_box stat-card" style="flex:1; margin:5px; padding:15px; text-align:center; border-radius:8px; border:1px solid #ddd; background:#f9fafb;">
<span style="font-size:14px; color:#555;">销售价格</span>
<h3 style="margin-top:8px; color:blue;">
<field name="selling_price" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</h3>
</div>
<div class="o_box stat-card" style="flex:1; margin:5px; padding:15px; text-align:center; border-radius:8px; border:1px solid #ddd; background:#f9fafb;">
<span style="font-size:14px; color:#555;">预期价格</span>
<h3 style="margin-top:8px; color:red;">
<field name="expected_price" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</h3>
</div>
<div class="o_box stat-card" style="flex:1; margin:5px; padding:15px; text-align:center; border-radius:8px; border:1px solid #ddd; background:#f9fafb;">
<span style="font-size:14px; color:#555;">最佳报价</span>
<h3 style="margin-top:8px; color:#333;">
<field name="best_price" widget="monetary" options="{'currency_field': 'currency_id'}"/>
</h3>
</div>
</div>
</div>
</div>
</sheet>
</form>
</field>
</record>
<!-- 搜索视图配置 -->
<record id="estate_property_search_view" model="ir.ui.view">
<field name="name">estate.property.search</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<search string="搜索">
<field name="name" />
<field name="postcode" />
<field name="expected_price" />
<field name="bedrooms" />
<field name="living_area" string="面积至少为(㎡)" filter_domain="[('living_area','>=',self)]" />
<field name="facades" />
<field name="garage" />
<separator />
<filter string="可用的" name="available" domain="['|',('state', '=', 'new'),('state', '=', 'offer_received')]" />
<group expand="1" string="Group By">
<filter string="邮编" name="postcode" context="{'group_by':'postcode'}" />
</group>
</search>
</field>
</record>
<!-- 看板试图配置 -->
<record id="estate_property_kanban" model="ir.ui.view">
<field name="model">estate.property</field>
<field name="arch" type="xml">
<kanban default_group_by="property_type_id" string="房地产信息" records_draggable="1">
<!-- 新增字段声明 tag也要显示-->
<field name="state" />
<templates>
<t t-name="kanban-box">
<div>
<div class="o_kanban_details">
<strong>
<field name="name" />
</strong>
<div class="o_kanban_description"> 预期价格:
<field name="expected_price" widget="monetary" options="{'currency_field': 'currency_id'}" />
<field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold,canceled" />
</div>
<div class="o_kanban_tags" style="margin-top: 5px;">
<field name="tag_ids" />
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
</odoo>
+12
View File
@@ -0,0 +1,12 @@
<odoo>
<record id="action_property_pdf_report" model="ir.actions.report">
<field name="name">房产详情报告</field>
<field name="model">estate.property</field>
<field name="binding_model_id" ref="model_estate_property"/>
<field name="report_type">qweb-pdf</field>
<field name="report_name">estate.report_property_template</field>
<field name="print_report_name">'房产报告-%s' % object.name</field>
<field name="binding_model_id" ref="model_estate_property" />
<field name="binding_type">report</field>
</record>
</odoo>
+68
View File
@@ -0,0 +1,68 @@
<odoo>
<template id="report_property_template">
<t t-call="web.html_container">
<!-- 假设单记录报表 -->
<t t-set="object" t-value="docs" />
<t t-debug="pdb"/>
<t t-call="web.external_layout">
<div class="page">
<!-- 头部信息 -->
<div>
<h2>房产基本信息</h2>
<table class="table table-bordered">
<tr>
<td>名称:</td>
<td>
<span t-field="object.name" />
</td>
<td>状态:</td>
<td>
<span t-field="object.state" />
</td>
</tr>
<tr>
<td>预期价格:</td>
<td>
<span t-field="object.expected_price"
t-options='{"widget": "monetary", "display_currency": object.currency_id}' />
</td>
<td>最佳报价:</td>
<td>
<span t-field="object.best_price"
t-options='{"widget": "monetary", "display_currency": object.currency_id}' />
</td>
</tr>
</table>
</div>
<!-- 报价明细 -->
<div t-if="object.offer_ids">
<h3>报价记录</h3>
<table class="table table-striped">
<thead>
<tr>
<th>买家</th>
<th>报价金额</th>
<th>创建日期</th>
</tr>
</thead>
<tbody>
<tr t-foreach="object.offer_ids" t-as="offer">
<td>
<span t-field="offer.partner_id.name" />
</td>
<td>
<span t-field="offer.price" />
</td>
<td>
<span t-field="offer.create_date" t-options='{"format": "yyyy-MM-dd"}' />
</td>
</tr>
</tbody>
</table>
</div>
</div>
</t>
</t>
</template>
</odoo>
+14
View File
@@ -0,0 +1,14 @@
<odoo>
<record id="res_users_view_form_estate" model="ir.ui.view">
<field name="name">res.users.view.form.estate</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form" />
<field name="arch" type="xml">
<page name="preferences" position="after">
<page string="房地产信息" name="estate_property_info">
<field name='property_ids' widget="one2many_list" />
</page>
</page>
</field>
</record>
</odoo>