Skip to content

Commit 57d1ae4

Browse files
committed
[ADD] add estate app with empty view
[ADD] added views of adding, listing properties and search filters [ADD] added 'offers' and 'other info' tabs in the property form and some new attributes added the tags and property types, buyer, amd seller models and linked them in the property model. also make view of offers list [ADD] compute fields in property models add the `total_area` and the `best_price` as computed attributes in the property modules and finish thier configuration added `validity` and `date_deadline` in the offer model as inverse computed attributes and configure them [FIX] styling issues replaced harcooded font style with bootstrap classes, and make the code follow sake_case naming [ADD] constrains to property fields and added some buttons for interactions ad constrains in the `selling_price, expected_price` attributes in the property model to be positive and the `name, property_type_id` to be unique together added consrains in the `price` attribute in the offer model to also be positive removed unused imports [ADD] interactive UI effects to the app add colors to records according to its state, and hide some buttons if according to the step the property is in. added default filter to show available properties [ADD] action button between property typers and offer [ADD] invoicing model and model inheritance inherit and add some vield to the user model make property invoicing model that create invoice after selling property [ADD] kanban view chapter 14 [FIX] warnings and style issues fixed issue in the property type action button in the form view, the manifist data order was incorrect fixed the warning of batch create in th property offer, fixed some code style issues in the code [FIX] handeled comments in the PR - modify the way of getting the best price - removed hard coded styles - remove unwanted dependencies - make code more clear
1 parent fbf9ee9 commit 57d1ae4

20 files changed

+549
-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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
'name': 'Real Estate',
3+
'version': '0.0',
4+
'category': 'Sale/estate',
5+
'summary': 'Manage your Real Estate Assets',
6+
'license': 'LGPL-3',
7+
'application': True,
8+
'installable': True,
9+
'depends': ['base'],
10+
'data': [
11+
'security/ir.model.access.csv',
12+
'views/estate_property_views.xml',
13+
'views/estate_property_tag_views.xml',
14+
'views/estate_property_type_offer.xml',
15+
'views/estate_property_type_views.xml',
16+
'views/estate_user_views.xml',
17+
'views/estate_menus.xml',
18+
],
19+
}

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
2+
from . import estate_property_type
3+
from . import estate_property_tag
4+
from . import estate_property_offer
5+
from . import estate_user

estate/models/estate_property.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from dateutil.relativedelta import relativedelta
2+
3+
from odoo import api, fields, models
4+
from odoo.exceptions import UserError
5+
from odoo.tools.float_utils import float_compare
6+
7+
8+
class TestModel(models.Model):
9+
_name = 'estate.property'
10+
_description = 'Test Estate Model'
11+
_order = 'id desc'
12+
13+
name = fields.Char(required=True)
14+
description = fields.Text()
15+
postcode = fields.Char()
16+
date_availability = fields.Date(default=fields.Date.today() + relativedelta(months=3), copy=False)
17+
expected_price = fields.Float(required=True)
18+
selling_price = fields.Float(readonly=False, copy=False)
19+
bedrooms = fields.Integer(default=2)
20+
living_area = fields.Integer(string='Living Area (sqm)')
21+
facades = fields.Integer()
22+
garage = fields.Boolean()
23+
garden = fields.Boolean()
24+
garden_area = fields.Integer(string='Garden Area (sqm)')
25+
garden_orientation = fields.Selection(
26+
string='Garden Orientation',
27+
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')],
28+
)
29+
active = fields.Boolean(default=True)
30+
state = fields.Selection(
31+
string='State',
32+
selection=[
33+
('new', 'New'),
34+
('offer_received', 'Offer Received'),
35+
('offer_accepted', 'Offer Accepted'),
36+
('sold', 'Sold'),
37+
('cancelled', 'Cancelled'),
38+
],
39+
default='new',
40+
readonly=True,
41+
)
42+
property_type_id = fields.Many2one('estate.property.type', string='Property Type')
43+
seller_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user)
44+
buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False, readonly=True)
45+
tag_ids = fields.Many2many('estate.property.tag', string='Tags')
46+
offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers')
47+
total_area = fields.Integer(compute='_compute_total_area', string='Total Area (sqm)')
48+
best_price = fields.Float(compute='_compute_best_price')
49+
50+
_sql_constraints = [
51+
('check_expected_price', 'CHECK(expected_price > 0)', 'The expected price must be a strictly positive value.'),
52+
('check_selling_price', 'CHECK(selling_price >= 0)', 'The expected price must be a positive value'),
53+
('unique_tag_name_and_type', 'UNIQUE(name, property_type_id)', 'The Name and the type should be unique.'),
54+
]
55+
56+
@api.constrains('selling_price')
57+
def _check_date_end(self):
58+
for record in self:
59+
if record.state == 'offer_accepted' and float_compare(record.selling_price, 0.9 * record.expected_price, 2):
60+
raise UserError(self.env._('the seeling price must be atleast 90% of the expected price.'))
61+
62+
@api.depends('living_area', 'garden_area')
63+
def _compute_total_area(self):
64+
for record in self:
65+
record.total_area = record.living_area + record.garden_area
66+
67+
@api.depends('offer_ids.price')
68+
def _compute_best_price(self):
69+
for record in self:
70+
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0.0
71+
72+
@api.onchange('garden')
73+
def _onchange_garden(self):
74+
for record in self:
75+
if record.garden:
76+
record.garden_area = 10
77+
record.garden_orientation = 'north'
78+
else:
79+
record.garden_area = 0
80+
record.garden_orientation = ''
81+
82+
def action_sell_property(self):
83+
for record in self:
84+
if record.state == 'cancelled':
85+
raise UserError(self.env._('Cancelled properties cant be sold'))
86+
else:
87+
record.state = 'sold'
88+
return True
89+
90+
def action_cancel_property(self):
91+
for record in self:
92+
if record.state == 'sold':
93+
raise UserError(self.env._('Sold properties cant be cancelled'))
94+
else:
95+
record.state = 'cancelled'
96+
return True
97+
98+
@api.model
99+
def ondelete(self):
100+
if self.state in ['new', 'cancelled']:
101+
return super().ondelete()
102+
raise UserError(self.env._('You cannot delete a property unless cancelled.'))
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from datetime import timedelta
2+
3+
from odoo import api, fields, models
4+
from odoo.exceptions import UserError
5+
6+
7+
class TestModel(models.Model):
8+
_name = 'estate.property.offer'
9+
_description = 'Estate Model Offer'
10+
_order = 'price desc'
11+
12+
price = fields.Float(default=0.00)
13+
status = fields.Selection(copy=False, selection=[('accepted', 'Accepted'), ('refused', 'Refused')])
14+
partner_id = fields.Many2one('res.partner', required=True)
15+
property_id = fields.Many2one('estate.property', required=True)
16+
validity = fields.Integer(compute='_compute_validity', inverse='_inverse_validity', string='Validity (days)')
17+
date_deadline = fields.Date(string='Deadline')
18+
property_type_id = fields.Many2one(related='property_id.property_type_id', string='Property Type', store=True)
19+
20+
_sql_constraints = [
21+
('check_offer_price', 'CHECK(price > 0)', 'The offer price must be a strictly positive value.'),
22+
]
23+
24+
@api.depends('date_deadline')
25+
def _compute_validity(self):
26+
for record in self:
27+
if record.date_deadline:
28+
record.validity = (record.date_deadline - fields.Date.today()).days
29+
else:
30+
record.validity = 0
31+
32+
def _inverse_validity(self):
33+
for record in self:
34+
record.date_deadline = fields.Date.today()
35+
if record.validity:
36+
record.date_deadline += timedelta(days=record.validity)
37+
38+
def action_accept_offer(self):
39+
for record in self:
40+
if record.property_id.state in ['new', 'offer_received'] and record.status != 'refused':
41+
record.property_id.selling_price = record.price
42+
record.status = 'accepted'
43+
record.property_id.state = 'offer_accepted'
44+
else:
45+
message = {
46+
'sold': 'cannot accept an offer on a sold property',
47+
'cancelled': 'cannot accept an offer on a cancelled property',
48+
'offer_accepted': 'cannot accept another offer on an accepted property',
49+
}
50+
raise UserError(self.env._(message[record.property_id.state]))
51+
return True
52+
53+
def action_refuse_offer(self):
54+
for record in self:
55+
record.status = 'refused'
56+
return True
57+
58+
@api.model_create_multi
59+
def create(self, vals):
60+
for val in vals:
61+
if val['price'] <= self.env['estate.property'].browse(val['property_id']).best_price:
62+
raise UserError(self.env._('The offer price must be greater than the best offer.'))
63+
64+
if self.env['estate.property'].browse(val['property_id']).state == 'new':
65+
self.env['estate.property'].browse(val['property_id']).state = 'offer_received'
66+
return super().create(vals)

estate/models/estate_property_tag.py

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

estate/models/estate_property_type.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from odoo import api, fields, models
2+
3+
4+
class TestModel(models.Model):
5+
_name = 'estate.property.type'
6+
_description = 'Estate Model Type'
7+
_order = 'name'
8+
9+
name = fields.Char(required=True)
10+
property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties')
11+
sequence = fields.Integer('Sequence', default=1)
12+
offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers')
13+
offer_count = fields.Integer(compute='_compute_offer_count', string='Offers Count')
14+
15+
@api.depends('offer_ids')
16+
def _compute_offer_count(self):
17+
for record in self:
18+
record.offer_count = len(record.offer_ids)

estate/models/estate_user.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+
4+
class TestModel(models.Model):
5+
_inherit = 'res.users'
6+
7+
property_ids = fields.One2many('estate.property', 'seller_id', string='Properties')

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+
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1
3+
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
4+
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1
5+
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1

estate/views/estate_menus.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<menuitem id="estate_menu_root" name="Estate">
4+
<menuitem id="estate_advertisements_menu" name="Advertisements">
5+
<menuitem id="estate_property_estates" action="estate.estate_property_action" />
6+
</menuitem>
7+
<menuitem id="estate_settings_menu" name="Settings">
8+
<menuitem id="estate_property_type" action="estate.estate_property_type_action" />
9+
<menuitem id="estate_property_tag" action="estate.estate_property_tag_action" />
10+
</menuitem>
11+
</menuitem>
12+
</odoo>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<record id="estate_property_tag_view_form" model="ir.ui.view">
4+
<field name="name">estate.property.tag.view.form</field>
5+
<field name="model">estate.property.tag</field>
6+
<field name="arch" type="xml">
7+
<form string="property tag form">
8+
<sheet>
9+
<group>
10+
<field name="name" />
11+
</group>
12+
</sheet>
13+
</form>
14+
</field>
15+
</record>
16+
17+
<record id="estate_property_tag_view_list" model="ir.ui.view">
18+
<field name="name">estate.property.tag.view.list</field>
19+
<field name="model">estate.property.tag</field>
20+
<field name="arch" type="xml">
21+
<list string="property tag list" editable="bottom">
22+
<field name="name" />
23+
</list>
24+
</field>
25+
</record>
26+
27+
<record id="estate_property_tag_action" model="ir.actions.act_window">
28+
<field name="name">Property Tags</field>
29+
<field name="res_model">estate.property.tag</field>
30+
<field name="view_mode">list,form</field>
31+
</record>
32+
</odoo>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<record id="estate_property_offer_view_list" model="ir.ui.view">
4+
<field name="name">estate.property.offer.view.list</field>
5+
<field name="model">estate.property.offer</field>
6+
<field name="arch" type="xml">
7+
<list string="offers list" editable="bottom" decoration-success="status == 'accepted'"
8+
decoration-danger="status == 'refused'">
9+
<field name="price" />
10+
<field name="partner_id" />
11+
<field name="validity" />
12+
<field name="date_deadline" />
13+
<button name="action_accept_offer" type="object" icon="fa-check" title="accept"
14+
invisible="status" />
15+
<button name="action_refuse_offer" type="object" icon="fa-close" title="refuse"
16+
invisible="status" />
17+
</list>
18+
</field>
19+
</record>
20+
21+
<record id="estate_property_offer_view_form" model="ir.ui.view">
22+
<field name="name">estate.property.offer.view.form</field>
23+
<field name="model">estate.property.offer</field>
24+
<field name="arch" type="xml">
25+
<form string="property offer form">
26+
<sheet>
27+
<group>
28+
<field name="price" />
29+
<field name="status" />
30+
<field name="partner_id" />
31+
<field name="validity" />
32+
<field name="date_deadline" />
33+
</group>
34+
</sheet>
35+
</form>
36+
</field>
37+
</record>
38+
39+
<record id="estate_property_offer_action" model="ir.actions.act_window">
40+
<field name="name">Property Offer</field>
41+
<field name="res_model">estate.property.offer</field>
42+
<field name="view_mode">list,form</field>
43+
<field name="domain">[('property_type_id', '=', active_id)]</field>
44+
</record>
45+
</odoo>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?xml version="1.0"?>
2+
<odoo>
3+
<record id="estate_property_type_view_form" model="ir.ui.view">
4+
<field name="name">estate.property.type.view.form</field>
5+
<field name="model">estate.property.type</field>
6+
<field name="arch" type="xml">
7+
<form string="property type form">
8+
<sheet>
9+
<field name="name" class="display-4" />
10+
<button type="action" name="%(estate_property_offer_action)d" string="Offers"/>
11+
<notebook>
12+
<page string="Properties">
13+
<field name="property_ids" />
14+
</page>
15+
</notebook>
16+
</sheet>
17+
</form>
18+
</field>
19+
</record>
20+
21+
<record id="estate_property_type_view_list" model="ir.ui.view">
22+
<field name="name">estate.property.type.view.list</field>
23+
<field name="model">estate.property.type</field>
24+
<field name="arch" type="xml">
25+
<list string="property type list">
26+
<field name="name" string="Name" />
27+
<field name="offer_ids" string="Offers" />
28+
<field name="offer_count" string="Offers Count" />
29+
<field name="sequence" string="Sequence" />
30+
</list>
31+
</field>
32+
</record>
33+
34+
<record id="estate_property_type_action" model="ir.actions.act_window">
35+
<field name="name">Property Types</field>
36+
<field name="res_model">estate.property.type</field>
37+
<field name="view_mode">list,form</field>
38+
</record>
39+
</odoo>

0 commit comments

Comments
 (0)