Skip to content

Commit 05f2d4c

Browse files
committed
[IMP] real_estate: add stat button, decorations, and search filters for offers
Added stat button on property type to show related offers with domain filtering Applied decorations on offer and property list views for status indication Made 'offer' and 'tag' list views editable and added optional availability field Set default filter for 'Available' properties, improved living area search logic
1 parent 728a8e2 commit 05f2d4c

9 files changed

+257
-139
lines changed

real_estate/__manifest__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
],
1010
'application': True,
1111
'installable': True,
12-
'license':'LGPL-3',
12+
'license': 'LGPL-3',
1313
'data': [
1414
'security/ir.model.access.csv',
1515
'view/estate_property_views.xml',

real_estate/models/estate_property.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
from datetime import date, timedelta
2-
from odoo import fields, models
3-
from odoo.exceptions import UserError
2+
from odoo import api, fields, models
3+
from odoo.exceptions import UserError,ValidationError
4+
from odoo.tools.float_utils import float_compare, float_is_zero
45

56

67
class EstateProperty(models.Model):
78
_name = "estate.property"
89
_description = "Real Estate Property"
10+
_order = "id desc"
911

1012
name = fields.Char(default="Unknown", required=True)
1113
description = fields.Text(string="Description")
1214
postcode = fields.Char(string="Postcode")
1315
availability_date = fields.Date(default=lambda self: date.today() + timedelta(days=90), copy=False)
14-
expected_price = fields.Float(string="Expected Price",required=True)
16+
expected_price = fields.Float(string="Expected Price", required=True)
1517
selling_price = fields.Float(readonly=True, copy=False)
18+
_sql_constraints = [
19+
('check_expected_price_positive', 'CHECK(expected_price > 0)', 'The expected price must be strictly positive.'),
20+
('check_selling_price_positive', 'CHECK(selling_price >= 0)', 'The selling price must be positive.')
21+
]
22+
1623
bedrooms = fields.Integer(default=2)
1724
living_area = fields.Float(string="Living Area")
1825
facades = fields.Integer(string="Facades")
@@ -49,6 +56,15 @@ class EstateProperty(models.Model):
4956
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers")
5057
best_price = fields.Float(string="Best Offer", compute="_compute_best_price")
5158

59+
@api.constrains('selling_price', 'expected_price')
60+
def _check_selling_price(self):
61+
for record in self:
62+
if float_is_zero(record.selling_price, precision_digits=2):
63+
continue
64+
min_acceptable_price = 0.9 * record.expected_price
65+
if float_compare(record.selling_price, min_acceptable_price, precision_digits=2) < 0:
66+
raise ValidationError("Selling price cannot be lower than 90% of the expected price.")
67+
5268
@api.depends('living_area', 'garden_area')
5369
def _compute_total_area(self):
5470
for record in self:
@@ -68,7 +84,7 @@ def _onchange_garden(self):
6884
else:
6985
self.garden_area = 0
7086
self.garden_orientation = False
71-
87+
7288
def action_cancel(self):
7389
for record in self:
7490
if record.state == 'sold':
@@ -81,4 +97,4 @@ def action_sold(self):
8197
if record.state == 'cancelled':
8298
raise UserError("Cancelled properties cannot be sold.")
8399
record.state = 'sold'
84-
return True
100+
return True

real_estate/models/estate_property_offer.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,32 @@
66
class EstatePropertyOffer(models.Model):
77
_name = "estate.property.offer"
88
_description = "Property Offer"
9+
_order = "price desc"
910

1011
price = fields.Float(string="Offer Price")
11-
status = fields.Selection(
12-
[('accepted', 'Accepted'), ('refused', 'Refused')],
13-
string="Status",
14-
copy=False
15-
)
12+
state = fields.Selection([
13+
('new', 'New'),
14+
('accepted', 'Accepted'),
15+
('refused', 'Refused')
16+
], string="State",default='new', required=True)
17+
_sql_constraints = [
18+
('check_price', 'CHECK(price > 0)', 'The offer price must be strictly positive.'),
19+
]
1620
partner_id = fields.Many2one('res.partner', string="Partner", required=True)
1721
property_id = fields.Many2one('estate.property', string="Property", required=True)
18-
22+
property_type_id = fields.Many2one(
23+
'estate.property.type',
24+
related="property_id.property_type_id",
25+
store=True,
26+
readonly=True
27+
)
1928
validity = fields.Integer(default=7)
2029
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True)
21-
2230
@api.depends('create_date', 'validity')
2331
def _compute_date_deadline(self):
2432
for record in self:
2533
create_date = record.create_date or fields.Datetime.now()
2634
record.date_deadline = create_date.date() + timedelta(days=record.validity)
27-
2835
def _inverse_date_deadline(self):
2936
for record in self:
3037
create_date = record.create_date or fields.Datetime.now()
@@ -35,15 +42,14 @@ def action_accept(self):
3542
for offer in self:
3643
if offer.property_id.buyer_id:
3744
raise UserError("An offer has already been accepted.")
38-
offer.status = 'accepted'
45+
offer.state = 'accepted'
3946
offer.property_id.write({
4047
'selling_price': offer.price,
4148
'buyer_id': offer.partner_id.id,
4249
'state': 'offer_accepted'
4350
})
4451
return True
45-
4652
def action_refuse(self):
4753
for offer in self:
48-
offer.status = 'refused'
49-
return True
54+
offer.state = 'refused'
55+
return True
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
from odoo import models, fields
22

3+
34
class EstatePropertyTag(models.Model):
45
_name = "estate.property.tag"
56
_description = "Property Tag"
7+
_order = "name"
68

79
name = fields.Char(string="Name", required=True)
10+
color = fields.Integer('Color')
11+
_sql_constraints = [
12+
('unique_tag_name', 'UNIQUE(name)', 'A property tag name must be unique')
13+
]
Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
1-
from odoo import models, fields
1+
from odoo import models, fields, api
2+
23

34
class EstatePropertyType(models.Model):
45
_name = 'estate.property.type'
56
_description = 'Property Type'
7+
_order = "sequence, name"
68

7-
name = fields.Char(required=True)
9+
name = fields.Char(required=True)
10+
_sql_constraints = [
11+
('unique_property_type', 'UNIQUE(name)', 'A property type name must be unique')
12+
]
13+
property_ids = fields.One2many("estate.property", "property_type_id", string="Properties")
14+
sequence = fields.Integer(string="Sequence", default=10)
15+
offer_ids = fields.One2many(
16+
"estate.property.offer",
17+
"property_type_id",
18+
string="Offers"
19+
)
20+
offer_count = fields.Integer(
21+
string="Offers Count",
22+
compute="_compute_offer_count"
23+
)
24+
@api.depends("offer_ids")
25+
def _compute_offer_count(self):
26+
for record in self:
27+
record.offer_count = len(record.offer_ids)
Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,53 @@
11
<odoo>
2-
2+
<data>
33
<record id="action_estate_property_offer" model="ir.actions.act_window">
44
<field name="name">Real estate offer</field>
55
<field name="res_model">estate.property.offer</field>
66
<field name="view_mode">list,form</field>
7+
<field name="domain">[('property_type_id', '=', active_id)]</field>
78
</record>
8-
<!-- Form View for Offer -->
9+
910
<record id="view_estate_property_offer_form" model="ir.ui.view">
1011
<field name="name">estate.property.offer.form</field>
1112
<field name="model">estate.property.offer</field>
1213
<field name="arch" type="xml">
1314
<form string="Property Offer">
14-
<header>
15-
<button name="action_accept" type="object" string="Accept" class="btn-primary"/>
16-
<button name="action_refuse" type="object" string="Refuse" class="btn-secondary"/>
15+
<header>
16+
<button name="action_accept" string="Accept" type="object"
17+
class="btn-primary"
18+
invisible="state != 'accepted'"/>
19+
20+
<button name="action_refuse" string="Refuse" type="object"
21+
class="btn-secondary"
22+
invisible="state == 'accepted'"/>
1723
</header>
24+
1825
<sheet>
1926
<group>
2027
<field name="price"/>
2128
<field name="partner_id"/>
22-
<field name="status"/>
29+
<field name="state"/>
2330
</group>
2431
</sheet>
2532
</form>
2633
</field>
2734
</record>
2835

29-
<!-- List View for Offer -->
36+
3037
<record id="view_estate_property_offer_list" model="ir.ui.view">
3138
<field name="name">estate.property.offer.list</field>
3239
<field name="model">estate.property.offer</field>
3340
<field name="arch" type="xml">
34-
<list string="Offers">
41+
<list editable="bottom" string="Offers"
42+
decoration-danger="state == 'refused'"
43+
decoration-success="state == 'accepted'">
3544
<field name="price"/>
3645
<field name="partner_id"/>
37-
<field name="status"/>
46+
<field name="state" invisible="1"/>
3847
<button name="action_accept" type="object" string="" icon="fa-check"/>
3948
<button name="action_refuse" type="object" string="" icon="fa-times"/>
4049
</list>
4150
</field>
4251
</record>
52+
</data>
4353
</odoo>
Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22

33
<odoo>
4-
<!-- Action for Property Tags -->
4+
<record id="view_estate_property_tag_list" model="ir.ui.view">
5+
<field name="name">estate.property.tag.list</field>
6+
<field name="model">estate.property.tag</field>
7+
<field name="arch" type="xml">
8+
<list editable="bottom">
9+
<field name="name"/>
10+
<field name="color" widget="color_picker"/>
11+
</list>
12+
</field>
13+
</record>
14+
515
<record id="action_estate_property_tag" model="ir.actions.act_window">
6-
<field name="name">Property Tags</field>
7-
<field name="res_model">estate.property.tag</field>
8-
<field name="view_mode">list,form</field>
9-
</record>
16+
<field name="name">Property Tags</field>
17+
<field name="res_model">estate.property.tag</field>
18+
<field name="view_mode">list,form</field>
19+
</record>
1020
</odoo>
Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,53 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
32
<odoo>
3+
<data>
44
<record id="estate_property_type_action" model="ir.actions.act_window">
55
<field name="name">Property Types</field>
66
<field name="res_model">estate.property.type</field>
77
<field name="view_mode">list,form</field>
88
</record>
9-
10-
9+
<record id="estate_property_type_form_view" model="ir.ui.view">
10+
<field name="name">estate.property.type.form</field>
11+
<field name="model">estate.property.type</field>
12+
13+
<field name="arch" type="xml">
14+
<form string="Property Type">
15+
<div class="oe_button_box" name="button_box">
16+
<button name="%(action_estate_property_offer)d"
17+
type="action"
18+
class="oe_stat_button"
19+
icon="fa-money" title="Action">
20+
<field name="offer_count" widget="statinfo" string="Offers"/>
21+
</button>
22+
</div>
23+
<sheet>
24+
<group>
25+
<field name="name"/>
26+
</group>
27+
<notebook>
28+
<page string="Properties">
29+
<field name="property_ids">
30+
<list string="Properties">
31+
<field name="name"/>
32+
<field name="expected_price"/>
33+
<field name="state"/>
34+
</list>
35+
</field>
36+
</page>
37+
</notebook>
38+
</sheet>
39+
</form>
40+
</field>
41+
</record>
42+
<record id="estate_property_type_tree_view" model="ir.ui.view">
43+
<field name="name">estate.property.type.tree</field>
44+
<field name="model">estate.property.type</field>
45+
<field name="arch" type="xml">
46+
<list string="Property Types">
47+
<field name="sequence" widget="handle"/>
48+
<field name="name"/>
49+
</list>
50+
</field>
51+
</record>
52+
</data>
1153
</odoo>

0 commit comments

Comments
 (0)