Skip to content

Commit 6626196

Browse files
committed
[ADD] estate: new module to manage estate
1 parent fbf9ee9 commit 6626196

20 files changed

+500
-0
lines changed

estate/__init__.py

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

estate/__manifest__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
'name': "Estate",
3+
'depends': ['base'],
4+
'application': True,
5+
'installable': True,
6+
'data': [
7+
'security/ir.model.access.csv',
8+
'views/estate_property_views.xml',
9+
'views/estate_property_type_views.xml',
10+
'views/estate_property_offer_views.xml',
11+
'views/estate_property_tag_views.xml',
12+
'views/estate_menus.xml',
13+
]
14+
}

estate/models/__init__.py

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

estate/models/estate_property.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from datetime import date, timedelta
2+
3+
from odoo import api, fields, models
4+
from odoo.exceptions import UserError, ValidationError
5+
from odoo.tools.float_utils import float_compare, float_is_zero
6+
7+
class EstateProperty(models.Model):
8+
_name = 'estate.property'
9+
_description = "Real Estate Property"
10+
11+
_sql_constraints = [
12+
('check_expected_price', 'CHECK(expected_price > 0)', "Expected price must be strictly positive."),
13+
('check_selling_price', 'CHECK(selling_price >= 0)', "Selling price must be strictly positive.")
14+
]
15+
16+
name = fields.Char(string="Title", required=True)
17+
description = fields.Text()
18+
postcode = fields.Char()
19+
date_availability = fields.Date(copy=False, default=lambda self: date.today() + timedelta(days=90))
20+
expected_price = fields.Float(required=True)
21+
selling_price = fields.Float(readonly=True, copy=False)
22+
bedrooms = fields.Integer(default=2)
23+
living_area = fields.Integer(string="Living Area (sqm)")
24+
facades = fields.Integer(string="Facade")
25+
garage = fields.Boolean()
26+
garden = fields.Boolean()
27+
garden_area = fields.Integer(string="Garden Area (sqm)")
28+
garden_orientation = fields.Selection(
29+
selection=[
30+
('north', "North"),
31+
('south', "South"),
32+
('east', "East"),
33+
('west', "West"),
34+
],
35+
string="Garden Orientation"
36+
)
37+
available = fields.Boolean(default=True)
38+
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
39+
salesman_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user)
40+
buyer_id = fields.Many2one("res.partner", string="Buyer")
41+
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
42+
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
43+
state = fields.Selection(
44+
selection=[
45+
('new', "New"),
46+
('offer_received', "Received"),
47+
('offer_accepted', "Accepted"),
48+
('sold', "Sold"),
49+
('cancelled', "Cancelled"),
50+
('refused', "Refused")
51+
],
52+
required=True,
53+
copy=False,
54+
default='new'
55+
)
56+
note = fields.Text(string="Special mention about the property.")
57+
total_area = fields.Integer(string="Total Area (sqm)", compute="_compute_total_area")
58+
best_offer = fields.Float(string="Best Offer", compute="_compute_best_price")
59+
60+
@api.depends("living_area", "garden_area")
61+
def _compute_total_area(self):
62+
for record in self:
63+
record.total_area = record.living_area + record.garden_area
64+
65+
@api.depends("offer_ids.price")
66+
def _compute_best_price(self):
67+
for record in self:
68+
if record.offer_ids:
69+
record.best_offer = max(record.offer_ids.mapped("price"))
70+
else:
71+
record.best_offer = 0.0
72+
73+
@api.onchange("garden")
74+
def _onchange_garden(self):
75+
if self.garden:
76+
self.garden_area = 10
77+
self.garden_orientation = 'north'
78+
else:
79+
self.garden_area = 0
80+
self.garden_orientation = None
81+
82+
def cancel_property_button(self):
83+
for record in self:
84+
if record.state != 'sold':
85+
record.state = 'cancelled'
86+
else:
87+
raise UserError("You cannot cancel a sold property.")
88+
89+
def sold_property_button(self):
90+
for record in self:
91+
if record.state != 'cancelled':
92+
record.state = 'sold'
93+
else:
94+
raise UserError("You cannot sell a cancelled property.")
95+
96+
@api.constrains('expected_price')
97+
def _check_expected_price_positive(self):
98+
for record in self:
99+
if record.expected_price <= 0:
100+
raise ValidationError("Expected price must be strictly positive.")
101+
102+
@api.constrains('selling_price', 'expected_price')
103+
def _check_selling_price_margin(self):
104+
for record in self:
105+
if float_is_zero(record.selling_price, precision_digits=2):
106+
continue
107+
min_acceptable_price = record.expected_price * 0.9
108+
if float_compare(record.selling_price, min_acceptable_price, precision_digits=2) < 0:
109+
raise ValidationError("The selling price cannot be lower than 90% of the expected price.")
110+
111+
@api.depends(at_uninstall=False)
112+
def check_delete(self):
113+
for record in self:
114+
if record.state not in ['new', 'cancelled']:
115+
raise UserError("YOU CANNOT DELETE A PROPERTY THAT IS NOT NEW OR CANCELLED")
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from datetime import date, timedelta
2+
3+
from odoo import api, fields, models
4+
from odoo.exceptions import UserError
5+
6+
7+
class EstatePropertyOffer(models.Model):
8+
_name = "estate.property.offer"
9+
_description = "Estate property offer"
10+
11+
price = fields.Float()
12+
status = fields.Selection(
13+
selection=[
14+
('accepted', "Accepted"),
15+
('refused', "Refused")
16+
],
17+
copy=False
18+
)
19+
partner_id = fields.Many2one("res.partner", required=True)
20+
property_id = fields.Many2one("estate.property", required=True)
21+
validity = fields.Integer(default=7)
22+
deadline = fields.Date(string="Deadline", compute="_compute_deadline", inverse="_inverse_deadline")
23+
24+
@api.depends("validity")
25+
def _compute_deadline(self):
26+
for record in self:
27+
record.deadline = date.today() + timedelta(days=record.validity)
28+
29+
def _inverse_deadline(self):
30+
for record in self:
31+
if record.deadline:
32+
record.validity = (record.deadline - date.today()).days
33+
34+
def offer_accept(self):
35+
if 'accepted' in self.mapped("property_id.offer_ids.status"):
36+
raise UserError("An offer is already accepted.")
37+
for record in self:
38+
record.status = 'accepted'
39+
record.property_id.state = 'offer_accepted'
40+
record.property_id.buyer_id = record.partner_id
41+
record.property_id.selling_price = record.price
42+
43+
def offer_refuse(self):
44+
for record in self:
45+
record.status = 'refused'

estate/models/estate_property_tag.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from odoo import fields,models
2+
3+
class estate_property_tag(models.Model):
4+
_name = "estate.property.tag"
5+
_description = "Estate property tag file"
6+
7+
name = fields.Char('Name',required=True)
8+
color = fields.Integer()

estate/models/estate_property_type.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from odoo import api, fields, models
2+
3+
class estate_property_type(models.Model):
4+
_name = "estate.property.type"
5+
_description = "Property type models file"
6+
7+
name = fields.Char("Name", required=True)
8+
9+
_sql_constraints = [
10+
('unique_type_name', 'UNIQUE(name)', 'The name should be unique')
11+
]
12+
13+
_order = "name"
14+
15+
property_id = fields.One2many("estate.property", "property_type_id")
16+
offer_id = fields.One2many("estate.property.offer", "property_id")
17+
offer_counts = fields.Integer(compute="_compute_offer_counts")
18+
19+
@api.depends("offer_id")
20+
def _compute_offer_counts(self):
21+
for record in self:
22+
record.offer_counts = len(record.offer_id)

estate/models/res_users.py

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 res_users(models.Model):
4+
_inherit = 'res.users'
5+
6+
property_ids = fields.One2many('estate.property', "salesman_id", domain="('state','=','new'),('state','=','offer_received')")
7+

estate/security/ir.model.access.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
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_offer","access_estate_property_offer","estate.model_estate_property_offer","base.group_user",1,1,1,1
5+
"estate.access_estate_property_tag","access_estate_property_tag","estate.model_estate_property_tag","base.group_user",1,1,1,1

estate/views/estate_menus.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<menuitem id="estate_menu_root" name="Estate">
4+
<menuitem id="estate_menu_advertissements" name="Advertissement">
5+
<menuitem id="estate_menu_properties" name="Property" action="estate_property_action"/>
6+
<menuitem id="estate_menu_properties_offer" name="Offers" action="estate_property_offer_view"/>
7+
</menuitem>
8+
9+
<menuitem id="estate_menu_settings" name="Settings" sequence="20">
10+
<menuitem id="estate_menu_properties_type" name="Property Type" action="estate_property_type_view"/>
11+
<menuitem id="estate_menu_properties_tags" name="Property Tags" action="estate_property_tag_action"/>
12+
</menuitem>
13+
</menuitem>
14+
</odoo>

0 commit comments

Comments
 (0)