Skip to content

Commit b128776

Browse files
committed
[ADD] estate: Add the base estate and estate_account modules
- Add the base estate and estate_account modules to build auctioning upon
1 parent 4c650f3 commit b128776

28 files changed

+1065
-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: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
'name': 'Real Estate',
3+
'version': '1.0',
4+
'depends': ['base'],
5+
'author': 'Aryan Donga (ardo)',
6+
'description': 'Real Estate advertisement module',
7+
'application': True,
8+
'installable': True,
9+
'license': 'LGPL-3',
10+
'category': 'Real Estate/Brokerage',
11+
'data': [
12+
'security/estate_property_security.xml',
13+
'security/ir.model.access.csv',
14+
'report/estate_property_reports.xml',
15+
'report/estate_property_templates.xml',
16+
'views/estate_property_type_views.xml',
17+
'views/estate_property_tag_views.xml',
18+
'views/estate_property_offer_views.xml',
19+
'views/estate_property_views.xml',
20+
'views/estate_menus.xml',
21+
'views/res_users_views.xml',
22+
'data/estate.property.type.csv',
23+
],
24+
'demo': ['data/estate_property_demo.xml', 'data/estate_property_offer_demo.xml'],
25+
}

estate/data/estate.property.type.csv

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
id,name
2+
estate_property_type_residential,Residential
3+
estate_property_type_commercial,Commercial
4+
estate_property_type_land,Land
5+
estate_property_type_industrial,Industrial

estate/data/estate_property_demo.xml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<odoo>
2+
<record id="demo_property_1" model="estate.property">
3+
<field name="name">Big Villa</field>
4+
<field name="state">new</field>
5+
<field name="description">A nice and big Villa</field>
6+
<field name="postcode">12345</field>
7+
<field name="date_availability" eval="datetime(2020, 2, 2)"/>
8+
<field name="expected_price">1600000</field>
9+
<field name="bedrooms">6</field>
10+
<field name="living_area">100</field>
11+
<field name="facades">4</field>
12+
<field name="garage">True</field>
13+
<field name="garden">True</field>
14+
<field name="garden_area">100000</field>
15+
<field name="garden_orientation">south</field>
16+
<field name="property_type_id" ref="estate.estate_property_type_residential"/>
17+
</record>
18+
19+
<record id="demo_property_2" model="estate.property">
20+
<field name="name">Trailer Home</field>
21+
<field name="state">cancelled</field>
22+
<field name="description">Home in a trailer park</field>
23+
<field name="postcode">54321</field>
24+
<field name="date_availability" eval="datetime(1970, 1, 1)"/>
25+
<field name="expected_price">100000</field>
26+
<field name="selling_price">120000</field>
27+
<field name="bedrooms">1</field>
28+
<field name="living_area">10</field>
29+
<field name="facades">4</field>
30+
<field name="garage">False</field>
31+
<field name="property_type_id" ref="estate.estate_property_type_residential"/>
32+
</record>
33+
34+
<record id="demo_property_3" model="estate.property">
35+
<field name="name">Luxurious Penthouse</field>
36+
<field name="state">offer_received</field>
37+
<field name="description">A luxurious penthouse in the city center</field>
38+
<field name="postcode">67890</field>
39+
<field name="date_availability" eval="datetime(2020, 2, 2)"/>
40+
<field name="expected_price">2000000</field>
41+
<field name="bedrooms">6</field>
42+
<field name="living_area">3000</field>
43+
<field name="facades">4</field>
44+
<field name="garage">True</field>
45+
<field name="garden">True</field>
46+
<field name="garden_area">1000</field>
47+
<field name="garden_orientation">south</field>
48+
<field name="property_type_id" ref="estate.estate_property_type_residential"/>
49+
<field name="buyer_id" ref="base.res_partner_2"/>
50+
<field name="offer_ids" eval="[
51+
Command.create({
52+
'partner_id': ref('base.res_partner_2'),
53+
'price': 2500000,
54+
'validity': 14
55+
}),
56+
Command.create({
57+
'partner_id': ref('base.res_partner_12'),
58+
'price': 2650000,
59+
'validity': 14
60+
}),
61+
Command.create({
62+
'partner_id': ref('base.res_partner_2'),
63+
'price': 2700000,
64+
'validity': 15
65+
})
66+
]"/>
67+
</record>
68+
</odoo>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<odoo>
2+
<record id="demo_big_villa_offer_1" model="estate.property.offer">
3+
<field name="property_id" ref="estate.demo_property_1"/>
4+
<field name="partner_id" ref="base.res_partner_12"/>
5+
<field name="price">10000</field>
6+
<field name="validity">14</field>
7+
</record>
8+
9+
<record id="demo_big_villa_offer_2" model="estate.property.offer">
10+
<field name="property_id" ref="estate.demo_property_1"/>
11+
<field name="partner_id" ref="base.res_partner_12"/>
12+
<field name="price">1500000</field>
13+
<field name="validity">14</field>
14+
</record>
15+
16+
<record id="demo_big_villa_offer_3" model="estate.property.offer">
17+
<field name="property_id" ref="estate.demo_property_1"/>
18+
<field name="partner_id" ref="base.res_partner_2"/>
19+
<field name="price">1500001</field>
20+
<field name="validity">14</field>
21+
</record>
22+
23+
<function name="action_accept" model="estate.property.offer">
24+
<value eval="ref('demo_big_villa_offer_2')"/>
25+
</function>
26+
</odoo>

estate/models/__init__.py

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

estate/models/estate_property.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
from dateutil.relativedelta import relativedelta
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
5+
6+
7+
class EstateProperty(models.Model):
8+
_name = 'estate.property'
9+
_description = 'An individual estate property listing'
10+
_order = 'id desc'
11+
_sql_constraints = [
12+
(
13+
'estate_property_expected_price_positive',
14+
'CHECK(expected_price > 0)',
15+
'The expected price must be strictly positive.',
16+
),
17+
(
18+
'estate_property_selling_price_non_negative',
19+
'CHECK(selling_price >= 0)',
20+
'The selling price must be non negative.',
21+
),
22+
]
23+
24+
@api.constrains('selling_price', 'expected_price')
25+
def _check_selling_price(self):
26+
for record in self:
27+
if not float_is_zero(record.selling_price, 2) and (
28+
float_compare(record.selling_price, (record.expected_price * 0.9), 2)
29+
== -1
30+
):
31+
raise ValidationError(
32+
'The selling price must be greater than 90% of the expected price.'
33+
'You must reduce the expected price if you want to accept this offer'
34+
)
35+
36+
name = fields.Char('Title', required=True, default='Unknown')
37+
description = fields.Text('Description')
38+
postcode = fields.Char('Postcode')
39+
date_availability = fields.Date(
40+
'Available From',
41+
default=lambda self: fields.Date.today() + relativedelta(months=3),
42+
copy=False,
43+
)
44+
expected_price = fields.Float('Expected Price', required=True)
45+
selling_price = fields.Float('Selling Price', copy=False, readonly=True)
46+
bedrooms = fields.Integer('Bedrooms', default=2)
47+
living_area = fields.Integer('Living Area (sqm)')
48+
facades = fields.Integer('Facades')
49+
garage = fields.Boolean('Garage')
50+
garden = fields.Boolean('Garden')
51+
garden_area = fields.Integer('Garden Area (sqm)')
52+
garden_orientation = fields.Selection(
53+
[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')],
54+
'Garden Orientation',
55+
)
56+
last_seen = fields.Datetime('Last Seen', default=fields.Datetime.now)
57+
active = fields.Boolean('Active', default=True)
58+
state = fields.Selection(
59+
[
60+
('new', 'New'),
61+
('offer_received', 'Offer Received'),
62+
('offer_accepted', 'Offer Accepted'),
63+
('sold', 'Sold'),
64+
('cancelled', 'Cancelled'),
65+
],
66+
'Status',
67+
default='new',
68+
copy=False,
69+
required=True,
70+
)
71+
company_id = fields.Many2one(
72+
'res.company', required=True, default=lambda self: self.env.company
73+
)
74+
property_type_id = fields.Many2one('estate.property.type', 'Property Type')
75+
salesperson_id = fields.Many2one(
76+
'res.users', 'Salesperson', default=lambda self: self.env.uid
77+
)
78+
buyer_id = fields.Many2one('res.partner', 'Buyer', copy=False)
79+
tag_ids = fields.Many2many('estate.property.tag', string='Tags')
80+
offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers')
81+
best_price = fields.Float(
82+
'Best Price', copy=False, readonly=True, compute='_compute_best_price'
83+
)
84+
total_area = fields.Integer(
85+
compute='_compute_total_area', readonly=True, string='Total Area (sqm)'
86+
)
87+
88+
@api.depends('garden_area', 'living_area')
89+
def _compute_total_area(self):
90+
for record in self:
91+
record.total_area = record.living_area + record.garden_area
92+
93+
@api.depends('offer_ids.price')
94+
def _compute_best_price(self):
95+
for record in self:
96+
record.best_price = (
97+
max(record.offer_ids.mapped('price')) if record.offer_ids else 0
98+
)
99+
100+
@api.onchange('garden')
101+
def _onchange_garden(self):
102+
"""Set default garden area and orientation when garden is checked."""
103+
if self.garden:
104+
self.garden_area = 10
105+
self.garden_orientation = 'north'
106+
else:
107+
self.garden_area = 0
108+
self.garden_orientation = False
109+
110+
@api.onchange('offer_ids')
111+
def _onchange_offer_ids(self):
112+
if self.state == 'offer_received':
113+
if not self.offer_ids:
114+
self.state = 'new'
115+
return
116+
117+
if self.state != 'new':
118+
raise UserError(
119+
'You cannot modify offers when the property is not in New or Offer Received state.'
120+
)
121+
122+
def action_cancel(self):
123+
for record in self:
124+
if record.state == 'sold':
125+
raise UserError('You cannot cancel a sold property.')
126+
127+
record.state = 'cancelled'
128+
129+
return True
130+
131+
def action_sold(self):
132+
for record in self:
133+
if record.state == 'cancelled':
134+
raise UserError('You cannot mark a cancelled property as sold.')
135+
136+
if record.state != 'offer_accepted':
137+
raise UserError(
138+
'You cannot mark a property as sold without accepting an offer.'
139+
)
140+
141+
record.state = 'sold'
142+
143+
return True
144+
145+
@api.ondelete(at_uninstall=False)
146+
def _unlink_if_property_new_cancelled(self):
147+
"""Only allow New or Cancelled properties to be deleted."""
148+
for record in self:
149+
if record.state not in ['new', 'cancelled']:
150+
raise UserError(
151+
'You cannot delete a property that is not New or Cancelled.'
152+
)

0 commit comments

Comments
 (0)