Skip to content

Commit a3558da

Browse files
committed
[ADD] estate_property: Safeguard your code with unit tests
1 parent b4b2836 commit a3558da

7 files changed

Lines changed: 77 additions & 17 deletions

File tree

estate/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from . import models
2+
from . import tests

estate/models/estate_property.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ class EstateProperty(models.Model):
3939
state = fields.Selection(
4040
selection=[
4141
('new', "New"),
42-
('offer received', "Offer Received"),
43-
('offer accepted', "Offer Accepted"),
42+
('offer_received', "Offer Received"),
43+
('offer_accepted', "Offer Accepted"),
4444
('sold', "Sold"),
4545
('canceled', "Canceled")
4646
],
@@ -101,7 +101,7 @@ def _check_price(self):
101101
def _onchange_receive_offer(self):
102102
for record in self.filtered(lambda record: record.state == 'new'):
103103
if record.offer_ids:
104-
record.state = 'offer received'
104+
record.state = 'offer_received'
105105

106106
@api.onchange('garden')
107107
def _onchange_garden(self):
@@ -122,12 +122,11 @@ def _unlike_property(self):
122122

123123
def action_mark_as_sold(self):
124124
for record in self:
125-
if record.state != 'canceled':
126-
record.state = 'sold'
127-
else:
128-
raise UserError(self.env._(
129-
"Canceled properties cannot be sold!")
125+
if record.state != 'offer_accepted':
126+
raise ValidationError(self.env._(
127+
"Only properties with an accepted offer can be sold!")
130128
)
129+
record.state = 'sold'
131130

132131
def action_mark_as_canceled(self):
133132
for record in self:

estate/models/estate_property_offer.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from odoo import api, fields, models
2-
from odoo.exceptions import UserError
2+
from odoo.exceptions import UserError, ValidationError
33
from dateutil.relativedelta import relativedelta
44

55

@@ -57,6 +57,10 @@ def create(self, vals_list):
5757
for vals in vals_list:
5858
related_property = self.env['estate.property'].browse(
5959
vals['property_id'])
60+
if related_property.state in ['sold', 'canceled', 'offer_accepted']:
61+
raise ValidationError(self.env._(
62+
"You cannot make an offer on a sold or canceled property!")
63+
)
6064
for offer in related_property.offer_ids:
6165
if offer.price > vals['price']:
6266
raise UserError(self.env._("This offer price is lower than the current ones"))
@@ -73,7 +77,7 @@ def action_accept_offer(self):
7377
record.status = 'accepted'
7478
record.property_id.buyer_id = record.partner_id
7579
record.property_id.selling_price = record.price
76-
record.property_id.state = 'offer accepted'
80+
record.property_id.state = 'offer_accepted'
7781

7882
def action_refuse_offer(self):
7983
for record in self:

estate/models/res_users.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ class ResUsers(models.Model):
66
_name = 'res.users'
77

88
property_ids = fields.One2many('estate.property', 'seller_id',
9-
domain=['|', ('state', '=', 'new'), ('state', '=', 'offer received')])
9+
domain=['|', ('state', '=', 'new'), ('state', '=', 'offer_received')])

estate/tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import test_estate

estate/tests/test_estate.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from odoo.tests.common import TransactionCase
2+
from odoo.exceptions import ValidationError
3+
from odoo.tests import tagged
4+
5+
6+
@tagged('post_install', '-at_install')
7+
class TestEstate(TransactionCase):
8+
9+
@classmethod
10+
def setUpClass(cls):
11+
"""This runs once to set up the data for all tests in this class."""
12+
super().setUpClass()
13+
14+
cls.type_id = cls.env.ref('estate.estate_property_type_residential')
15+
16+
cls.property = cls.env['estate.property'].create({
17+
'name': 'Test Villa',
18+
'property_type_id': cls.type_id.id,
19+
'expected_price': 100000,
20+
'state': 'new',
21+
})
22+
23+
cls.buyer = cls.env['res.partner'].create({'name': 'John Doe'})
24+
25+
def test_01_create_offer_on_restricted_states(self):
26+
"""Test: Cannot create offer if property is sold or canceled"""
27+
for restricted_state in ['sold', 'canceled', 'offer_accepted']:
28+
self.property.state = restricted_state
29+
30+
with self.assertRaises(ValidationError, msg=f"Should fail on {restricted_state}"):
31+
self.env['estate.property.offer'].create({
32+
'property_id': self.property.id,
33+
'price': 50000,
34+
'partner_id': self.buyer.id,
35+
})
36+
37+
def test_02_sell_without_accepted_offer(self):
38+
"""Test: Cannot sell property if no offers are 'accepted'"""
39+
self.property.state = 'new'
40+
with self.assertRaises(ValidationError):
41+
self.property.action_mark_as_sold()
42+
43+
def test_03_successful_sell_flow(self):
44+
"""Test: Property marks as 'sold' correctly when an offer is accepted"""
45+
offer = self.env['estate.property.offer'].create({
46+
'property_id': self.property.id,
47+
'price': 90000,
48+
'partner_id': self.buyer.id,
49+
})
50+
51+
offer.action_accept_offer()
52+
53+
self.property.action_mark_as_sold()
54+
55+
self.assertEqual(self.property.state, 'sold', "The property should be in 'sold' state.")

estate/views/estate_property_views.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<field name="name">estate.property.list</field>
44
<field name="model">estate.property</field>
55
<field name="arch" type="xml">
6-
<list decoration-success="state == 'offer received' or state == 'offer accepted'" decoration-bf="state == 'offer accepted'" decoration-muted="state == 'sold'">
6+
<list decoration-success="state == 'offer_received' or state == 'offer_accepted'" decoration-bf="state == 'offer_accepted'" decoration-muted="state == 'sold'">
77
<field name="name"/>
88
<field name="property_type_id"/>
99
<field name="tag_ids" options="{'color_field': 'color'}" widget="many2many_tags"/>
@@ -23,9 +23,9 @@
2323
<field name="arch" type="xml">
2424
<form>
2525
<header>
26-
<button name="action_mark_as_sold" type="object" string="SOLD" invisible="state == 'sold' or state == 'canceled'"/>
26+
<button name="action_mark_as_sold" type="object" string="SOLD" invisible="state != 'offer_accepted'"/>
2727
<button name="action_mark_as_canceled" type="object" string="CANCEL" invisible="state == 'sold' or state == 'canceled'"/>
28-
<field name="state" widget="statusbar" statusbar_visible="new, offer received, offer accepted, sold"/>
28+
<field name="state" widget="statusbar" statusbar_visible="new, offer_received, offer_accepted, sold"/>
2929
</header>
3030
<sheet>
3131
<group>
@@ -61,7 +61,7 @@
6161
</group>
6262
</page>
6363
<page string="Offers">
64-
<field name="offer_ids" readonly="state == 'offer accepted'
64+
<field name="offer_ids" readonly="state == 'offer_accepted'
6565
or state == 'sold' or state == 'canceled'"/>
6666
</page>
6767
<page string="Ohter Info">
@@ -95,7 +95,7 @@
9595
<span style="margin-right: 3px;">Best Price:</span>
9696
<field name="best_offer"/>
9797
</div>
98-
<div t-if="record.state.raw_value == 'offer accepted'
98+
<div t-if="record.state.raw_value == 'offer_accepted'
9999
or record.state.raw_value == 'sold'">
100100
<span style="margin-right: 3px;">Selling Price:</span>
101101
<field name="selling_price"/>
@@ -123,7 +123,7 @@
123123
<field name="expected_price"/>
124124
<field name="facades"/>
125125

126-
<filter name="available" string="Available" domain="['|', ('state', '=', 'new'), ('state', '=', 'offer received')]"/>
126+
<filter name="available" string="Available" domain="['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]"/>
127127
<filter string="Postcode" name="postcode" context="{'group_by':'postcode'}"/>
128128
</search>
129129
</field>

0 commit comments

Comments
 (0)