Skip to content

Commit c67201f

Browse files
committed
[IMP] estate: add data constraints and UI Changes
- Added SQL constraints to ensure: - Property expected price is strictly positive - Property selling price is positive - Offer price is strictly positive - Property tag name and property type name are unique - Added Python constraint to prevent selling price from being set below 90% of expected price - Changes in UI: - Added inline list view for properties on property type form - Used statusbar widget for property state display - Defined default ordering for models and enabled manual ordering for property types via sequence field - Applied widget options to restrict creation/editing of property types from property form
1 parent 5d58f2a commit c67201f

9 files changed

+153
-27
lines changed

estate/__manifest__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,8 @@
66
'author':"Dhruvrajsinh Zala (zadh)",
77
'installable': True,
88
'application': True,
9-
'data':['security/ir.model.access.csv', 'views/estate_property_views.xml','views/estate_menus.xml','views/estate_property_type_views.xml','views/estate_property_tags.xml']
9+
'data':['security/ir.model.access.csv', 'views/estate_property_offers.xml',
10+
'views/estate_property_views.xml',
11+
'views/estate_menus.xml',
12+
'views/estate_property_type_views.xml','views/estate_property_tags.xml']
1013
}
Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,31 @@
1-
from odoo import models,fields
1+
from odoo import models,fields,api
22

33

44
class EstatePropertyTyeps(models.Model):
55
_name="estate.property.types"
66
_description="Types of Estate Property"
7+
_order="sequence,name"
78

8-
name=fields.Char(required=True)
9+
_unique_type_name = models.Constraint('UNIQUE(name)','Property type name must be unique.')
10+
11+
name=fields.Char(required=True)
12+
property_ids=fields.One2many('estate.property','property_type_id',string="Properties")
13+
sequence = fields.Integer(default=1)
14+
15+
offer_ids = fields.One2many(
16+
'estate.property.offer',
17+
'property_type_id',
18+
string="Offers"
19+
)
20+
21+
22+
offer_count = fields.Integer(
23+
string="Offer Count",
24+
compute='_compute_offer_count',
25+
store=True
26+
)
27+
28+
@api.depends('offer_ids')
29+
def _compute_offer_count(self):
30+
for record in self:
31+
record.offer_count = len(record.offer_ids)

estate/models/estate_property.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
from dateutil.relativedelta import relativedelta
22
from odoo import models,fields,api,_
3-
from odoo.exceptions import UserError
3+
from odoo.tools.float_utils import float_compare,float_is_zero
4+
from odoo.exceptions import UserError,ValidationError
45

56
class EstateProperty(models.Model):
67
_name = 'estate.property'
78
_description = 'Real Estate Property'
9+
_order = 'id desc'
10+
11+
12+
_check_expected_price = models.Constraint('CHECK(expected_price > 0)','The expected price must be positive.')
13+
_check_selling_price = models.Constraint('CHECK(selling_price >= 0)','The selling price must be positive.')
814

915
name = fields.Char(required=True,string="Title")
1016
description = fields.Text()
@@ -54,8 +60,6 @@ class EstateProperty(models.Model):
5460

5561
best_price = fields.Float(compute='_get_best_offer_price',store=True)
5662

57-
58-
5963
@api.depends("living_area","garden_area")
6064
def _compute_total_area(self):
6165
for record in self:
@@ -81,7 +85,6 @@ def action_set_state_sold(self):
8185
for record in self:
8286
if(record.state == 'cancelled'):
8387
raise UserError(_("Cancelled property cannot be sold."))
84-
print("Cancelled can't be sold")
8588
else:
8689
record.state = 'sold'
8790
return True
@@ -90,7 +93,18 @@ def action_set_state_cancel(self):
9093
for record in self:
9194
if(record.state == 'sold'):
9295
raise UserError(_("Sold property cannot be cancelled."))
93-
print("Sold can't be cancelled")
9496
else:
9597
record.state = 'cancelled'
96-
return True
98+
return True
99+
100+
@api.constrains('selling_price','expected_price')
101+
def _check_selling_price(self):
102+
for record in self:
103+
if not float_is_zero(record.selling_price,precision_digits=2):
104+
if float_compare(
105+
record.selling_price,
106+
record.expected_price * 0.9,
107+
precision_digits=2) < 0 :
108+
raise ValidationError(_("The selling price cannot be lower than 90% of the expected price"))
109+
110+

estate/models/estate_property_offer.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,20 @@
55
class EstatePropertyOffer(models.Model):
66
_name="estate.property.offer"
77
_description="Offers of Estate Property"
8+
_order = 'price desc'
9+
10+
_check_offer_price = models.Constraint('CHECK(price > 0)','The Offer price must be positive.')
11+
812
price = fields.Float()
913
status = fields.Selection([('accepted','Accepted'),('refused','Refused')],copy=False)
1014
partner_id = fields.Many2one('res.partner',string="Partner",required=True)
1115
property_id = fields.Many2one('estate.property',string="Property",required=True)
16+
property_type_id = fields.Many2one(
17+
'estate.property.types',
18+
related="property_id.property_type_id",
19+
string="Property Type",
20+
required=True)
21+
1222
validity = fields.Integer(default=7)
1323
date_deadline = fields.Date(compute="_compute_deadline",inverse="_inverse_deadline",store=True)
1424
@api.depends('validity')
@@ -32,6 +42,7 @@ def action_accept_offer(self):
3242
if existing:
3343
raise UserError(_("Another offer has been already accepted."))
3444
record.status = "accepted"
45+
record.property_id.state = "offer_accepted"
3546
record.property_id.selling_price = record.price
3647
record.property_id.buyer_id = record.partner_id.id
3748
def action_refuse_offer(self):

estate/models/estate_property_tags.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@
33
class EstatePropertyTags(models.Model):
44
_name="estate.property.tags"
55
_description="Estate Property Tags"
6+
_order = "name"
7+
8+
_unique_tag_name = models.Constraint('UNIQUE(name)','Tag name must be unique.')
69

710
name=fields.Char(required=True)
11+
color=fields.Integer(default=3)

estate/views/estate_menus.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
<menuitem id="menu_estate_properties_settings" name="Settings">
99
<menuitem id="estate_property_types" name="Property Types" action="estate_property_type_action"/>
1010
<menuitem id="estate_property_tags" name="Property Tags" action="estate_property_tags_action"/>
11-
</menuitem>
11+
</menuitem>
1212
</menuitem>
1313
</odoo>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<odoo>
2+
<record id="estate_property_offer_action" model="ir.actions.act_window">
3+
<field name="name">Property Offers</field>
4+
<field name="res_model">estate.property.offer</field>
5+
<field name="view_mode">list,form</field>
6+
<field name="domain">[('property_type_id', '=', active_id)]</field>
7+
</record>
8+
9+
10+
<record id="estate_property_offers_list" model="ir.ui.view">
11+
<field name="name">estate.property.offer.list</field>
12+
<field name="model">estate.property.offer</field>
13+
<field name="arch" type="xml">
14+
<list string="Property Tags">
15+
<field name="price"/>
16+
<field name="partner_id"/>
17+
<field name="validity"/>
18+
<field name="date_deadline"/>
19+
<button name="action_accept_offer" type="object" title="Accept" icon="fa-check" invisible="status in (
20+
'accepted','refused')"/>
21+
<button name="action_refuse_offer" type="object" title="Refuse" icon="fa-times" invisible="status in (
22+
'accepted','refused')"/>
23+
</list>
24+
</field>
25+
</record>
26+
27+
</odoo>

estate/views/estate_property_type_views.xml

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<field name="model">estate.property.types</field>
1414
<field name="arch" type="xml">
1515
<list string="Property Types">
16+
<field name="sequence" widget="handle"/>
1617
<field name="name"/>
1718
</list >
1819
</field>
@@ -25,9 +26,34 @@
2526
<field name="type">form</field>
2627
<field name="arch" type="xml">
2728
<form string="Property Types">
28-
<group>
29-
<field name="name"/>
30-
</group>
29+
<sheet>
30+
<div class="oe_button_box" name="button_box">
31+
<button name="%(estate_property_offer_action)d"
32+
type="action"
33+
string="Offers"
34+
icon="fa-envelope"
35+
context="{'default_property_type_id': id}"
36+
class="oe_stat_button">
37+
<field name="offer_count" widget="statinfo" string="Offers"/>
38+
</button>
39+
</div>
40+
<div class="oe_title">
41+
<h1>
42+
<field name="name" />
43+
</h1>
44+
</div>
45+
<notebook>
46+
<page string="Properties">
47+
<field name="property_ids">
48+
<list>
49+
<field name="name"/>
50+
<field name="expected_price"/>
51+
<field name="state"/>
52+
</list>
53+
</field>
54+
</page>
55+
</notebook>
56+
</sheet>
3157
</form>
3258
</field>
3359

estate/views/estate_property_views.xml

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,28 @@
33
<field name="name">Properties</field>
44
<field name="res_model">estate.property</field>
55
<field name="view_mode">list,form</field>
6+
<field name="context">{'search_default_available':1}</field>
67
</record>
78

89
<record id="estate_property_list_view" model="ir.ui.view">
910
<field name="name">estate.property.list</field>
1011
<field name="model">estate.property</field>
1112
<field name="type">list</field>
1213
<field name="arch" type="xml">
13-
<list string="Property">
14+
<list string="Property"
15+
decoration-success="state in ('offer_received','offer_accepted')"
16+
decoration-bf="state == 'offer_accepted'"
17+
decoration-muted="state == 'sold'"
18+
>
1419
<field name="name" />
20+
<field name="property_type_id"/>
1521
<field name="postcode" />
22+
<field name="tag_ids" widget="many2many_tags"/>
1623
<field name="bedrooms" />
1724
<field name="living_area" />
1825
<field name="expected_price"/>
1926
<field name="selling_price" />
20-
<field name="date_availability" />
27+
<field name="date_availability" optional="hide" />
2128
</list>
2229
</field>
2330
</record>
@@ -29,8 +36,11 @@
2936
<field name="arch" type="xml">
3037
<form string="Property">
3138
<header>
32-
<button name="action_set_state_sold" type="object" string="Sold"/>
33-
<button name="action_set_state_cancel" type="object" string="Cancel"/>
39+
<button name="action_set_state_sold" type="object" string="Sold" invisible="state in ('sold','cancelled')"/>
40+
<button name="action_set_state_cancel" type="object" string="Cancel" invisible="state in ('sold','cancelled')"/>
41+
42+
<field name="state" widget="statusbar" statusbar_visible="new,offer_received,offer_accepted,sold"/>
43+
3444
</header>
3545
<sheet>
3646
<div class="oe_title">
@@ -39,11 +49,11 @@
3949
</h1>
4050
</div>
4151
<div class='mb-3'>
42-
<field name="tag_ids" widget="many2many_tags"/>
52+
<field name="tag_ids" widget="many2many_tags" options="{'color_field': 'color'}" />
4353
</div>
4454
<group>
4555
<group>
46-
<field name="property_type_id"/>
56+
<field name="property_type_id" options="{'no_create':True,'no_edit':True}"/>
4757
<field name="postcode"/>
4858
<field name="date_availability"/>
4959
</group>
@@ -62,21 +72,25 @@
6272
<field name="facades"/>
6373
<field name="garage"/>
6474
<field name="garden"/>
65-
<field name="garden_area"/>
66-
<field name="garden_orientation"/>
75+
<field name="garden_area" invisible="not garden"/>
76+
<field name="garden_orientation" invisible="not garden"/>
6777
<field name="total_area" string="Total Area (sqm)"/>
6878
</group>
6979
</page>
7080
<page string="Offers">
71-
<field name="offer_ids">
72-
<list>
81+
<field name="offer_ids" readonly="state in ('offer_accepted','sold','cancelled')" >
82+
<list editable="bottom"
83+
decoration-success="status == 'accepted'"
84+
decoration-danger="status == 'refused'"
85+
>
7386
<field name="price"/>
7487
<field name="partner_id"/>
7588
<field name="validity"/>
7689
<field name="date_deadline"/>
77-
<button name="action_accept_offer" type="object" title="Accept" icon="fa-check"/>
78-
<button name="action_refuse_offer" type="object" title="Refuse" icon="fa-times"/>
79-
<field name="status"/>
90+
<button name="action_accept_offer" type="object" title="Accept" icon="fa-check" invisible="status in (
91+
'accepted','refused')"/>
92+
<button name="action_refuse_offer" type="object" title="Refuse" icon="fa-times" invisible="status in (
93+
'accepted','refused')"/>
8094

8195
</list>
8296
</field>
@@ -104,8 +118,9 @@
104118
<field name="postcode"/>
105119
<field name="expected_price"/>
106120
<field name="bedrooms"/>
107-
<field name="living_area"/>
121+
<field name="living_area" filter_domain="[('living_area','>=', self)]"/>
108122
<field name="facades"/>
123+
109124

110125
<filter name="available" string="Available" domain="[('state','in',['new','offer_received'])]"/>
111126

@@ -116,4 +131,7 @@
116131
</field>
117132
</record>
118133

134+
135+
136+
119137
</odoo>

0 commit comments

Comments
 (0)