Skip to content

Commit 1601aac

Browse files
committed
[ADD] real_estate: add offer status buttons and state transitions on properties
This commit introduces action buttons to accept or refuse offers in the real_estate module. When an offer is accepted, the property’s selling price, buyer, and state are updated accordingly. Other offers are automatically refused to ensure only one offer is accepted per property. Also added: - 'state' field on properties to manage lifecycle (new, offer_received, sold, cancelled) - Computed best price field from offers - Validity-based deadline logic using compute/inverse methods on offers - Safeguards preventing sale of cancelled properties and cancellation of sold ones
1 parent c48844b commit 1601aac

12 files changed

+257
-9
lines changed

real_estate/__manifest__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
'data': [
1111
'security/ir.model.access.csv',
1212
'views/estate_property_views.xml',
13+
'views/estate_property_type_view.xml',
14+
'views/estate_property_tag_view.xml',
15+
'views/estate_property_offer_view.xml',
1316
'views/estate_menus.xml'
1417
]
1518
}

real_estate/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
from . import estate_property
2+
from . import estate_property_type
3+
from . import estate_property_tag
4+
from . import estate_property_offer

real_estate/models/estate_property.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
from odoo import fields, models
1+
from odoo import fields, models, api
22
from datetime import date, timedelta
3+
from odoo.exceptions import UserError
34

45
class estate_property(models.Model):
56
_name = "estate.property"
67
_description = "Test Model"
78

9+
810
name = fields.Char(required=True)
911
description = fields.Text()
1012
postcode = fields.Char()
@@ -15,12 +17,17 @@ class estate_property(models.Model):
1517
)
1618
expected_price = fields.Float(required=True)
1719
selling_price = fields.Float(readonly=True, copy=False)
20+
21+
best_price = fields.Float(compute="_compute_best_price")
22+
1823
bedrooms = fields.Integer(default=2)
19-
living_area = fields.Integer()
24+
2025
facades = fields.Integer()
2126
garage = fields.Boolean()
2227
garden = fields.Boolean()
23-
garden_area = fields.Integer()
28+
living_area = fields.Float()
29+
garden_area = fields.Float()
30+
total_area = fields.Float(compute="_compute_total_area")
2431
garden_orientation = fields.Selection(
2532
string='Garden Orientation',
2633
selection=[('north', 'North'), ('south', 'South'),
@@ -40,6 +47,49 @@ class estate_property(models.Model):
4047
copy=False,
4148
default='new'
4249
)
50+
property_type = fields.Many2one("estate.property.type", string="Property Type", default=None)
51+
buyer = fields.Many2one("res.partner", string="Buyer", copy=False)
52+
seller = fields.Many2one("res.partner", string="Salesperson", default=lambda self: self.env.user)
53+
tag_ids = fields.Many2many("estate.property.tag", string="Tags", default=None)
54+
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
55+
56+
@api.depends("living_area", "garden_area")
57+
def _compute_total_area(self):
58+
for record in self:
59+
record.total_area = record.living_area + record.garden_area
60+
61+
62+
@api.depends("offer_ids.price")
63+
def _compute_best_price(self):
64+
for record in self:
65+
if record.offer_ids:
66+
record.best_price = max(record.offer_ids.mapped("price"))
67+
else:
68+
record.best_price = 0.0
69+
70+
71+
@api.onchange('garden')
72+
def _onchange_garden(self):
73+
if self.garden:
74+
self.garden_area = 10
75+
self.garden_orientation = 'north'
76+
else:
77+
self.garden_area = 0
78+
self.garden_orientation = False
79+
80+
81+
def action_sold(self):
82+
for record in self:
83+
if record.state == 'cancelled':
84+
raise UserError("Cancelled property cannot be sold.")
85+
record.state = 'sold'
86+
return True
87+
88+
def action_cancel(self):
89+
for record in self:
90+
if record.state == 'sold':
91+
raise UserError("Sold property cannot be cancelled.")
92+
record.state = 'cancelled'
93+
return True
4394

44-
4595

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from odoo import models, fields, api
2+
from datetime import timedelta
3+
from odoo.exceptions import UserError
4+
5+
class EstatePropertyOffer(models.Model):
6+
_name = "estate.property.offer"
7+
_description = "Property Offer"
8+
9+
price = fields.Float()
10+
status = fields.Selection([
11+
('accepted', 'Accepted'),
12+
('refused', 'Refused')
13+
])
14+
partner_id = fields.Many2one("res.partner", string="Partner", required=True)
15+
property_id = fields.Many2one("estate.property", string="Property", required=True)
16+
17+
validity = fields.Integer(default=7)
18+
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline")
19+
20+
@api.depends("create_date", "validity")
21+
def _compute_date_deadline(self):
22+
for record in self:
23+
create_date = record.create_date or fields.Date.today()
24+
record.date_deadline = create_date + timedelta(days=record.validity)
25+
print("Computed date_deadline:", record.date_deadline)
26+
27+
def _inverse_date_deadline(self):
28+
for record in self:
29+
create_date = record.create_date.date() if record.create_date else fields.Date.today()
30+
record.validity = (record.date_deadline - create_date).days
31+
32+
33+
def action_accept(self):
34+
for record in self:
35+
if record.property_id.state == 'sold':
36+
raise UserError("You cannot accept an offer on a sold property.")
37+
record.status = 'accepted'
38+
record.property_id.selling_price = record.price
39+
record.property_id.buyer = record.partner_id
40+
record.property_id.state = 'offer_accepted'
41+
42+
other_offers = self.search([
43+
('property_id', '=', record.property_id.id),
44+
('id', '!=', record.id)
45+
])
46+
other_offers.write({'status': 'refused'})
47+
48+
def action_refuse(self):
49+
for offer in self:
50+
offer.status = 'refused'
51+
return True
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from odoo import fields, models
2+
3+
class estate_property_tag(models.Model):
4+
_name = "estate.property.tag"
5+
_description = "Property Tag"
6+
7+
name = fields.Char(required=True)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from odoo import fields, models
2+
3+
4+
class estate_property_type(models.Model):
5+
_name = "estate.property.type"
6+
_description = "property type"
7+
name = fields.Char(required=True)
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2-
access_estate_property_user,access.estate.property,model_estate_property,,1,1,1,1
2+
access_estate_property_user,access.estate.property,model_estate_property,base.group_user,1,1,1,1
3+
access_estate_property_type_user,access.estate.property.type,model_estate_property_type,base.group_user,1,1,1,1
4+
access_estate_property_tag_user,access.estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1
5+
access_estate_property_offer_user,access.estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1

real_estate/views/estate_menus.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,16 @@
1212
action="action_estate_property"
1313
sequence="10" />
1414

15+
<menuitem id="estate_setting" name="Settings" parent="estate_menu_root" />
16+
17+
<menuitem id="estate_property_type"
18+
name="Property Types"
19+
parent="estate_setting"
20+
action="action_estate_property_type" />
21+
22+
<menuitem id="estate_menu_property_tag"
23+
name="Property Tags"
24+
parent="estate_setting"
25+
action="action_estate_property_tag" />
26+
1527
</odoo>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<odoo>
3+
<record id="view_estate_property_offer_list" model="ir.ui.view">
4+
<field name="name">estate.property.offer.list</field>
5+
<field name="model">estate.property.offer</field>
6+
<field name="arch" type="xml">
7+
<list>
8+
<field name="price" />
9+
<field name="partner_id" />
10+
<field name="validity" />
11+
<field name="date_deadline" />
12+
<field name="status" />
13+
</list>
14+
</field>
15+
</record>
16+
17+
<record id="view_estate_property_offer_form" model="ir.ui.view">
18+
<field name="name">estate.property.offer.form</field>
19+
<field name="model">estate.property.offer</field>
20+
<field name="arch" type="xml">
21+
<form>
22+
<header>
23+
<button name="action_accept" type="object" string="Accept" class="btn-primary"/>
24+
<button name="action_refuse" type="object" string="Refuse" class="btn-secondary"/>
25+
</header>
26+
27+
<sheet>
28+
<group>
29+
<field name="price" />
30+
<field name="partner_id" />
31+
<field name="validity" />
32+
<field name="date_deadline" />
33+
<field name="status" />
34+
</group>
35+
</sheet>
36+
</form>
37+
</field>
38+
</record>
39+
</odoo>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<odoo>
3+
<record id="view_estate_property_tag_list" model="ir.ui.view">
4+
<field name="name">estate.property.tag.list</field>
5+
<field name="model">estate.property.tag</field>
6+
<field name="arch" type="xml">
7+
<list>
8+
<field name="name"/>
9+
</list>
10+
</field>
11+
</record>
12+
13+
<record id="view_estate_property_tag_form" model="ir.ui.view">
14+
<field name="name">estate.property.tag.form</field>
15+
<field name="model">estate.property.tag</field>
16+
<field name="arch" type="xml">
17+
<form>
18+
<field name="name"/>
19+
</form>
20+
</field>
21+
</record>
22+
23+
<record id="action_estate_property_tag" model="ir.actions.act_window">
24+
<field name="name">Property Tags</field>
25+
<field name="res_model">estate.property.tag</field>
26+
<field name="view_mode">list,form</field>
27+
</record>
28+
29+
30+
</odoo>

0 commit comments

Comments
 (0)