Skip to content

[ADD] Estate: create a new module for real estate management #786

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: 18.0
Choose a base branch
from
129 changes: 0 additions & 129 deletions .gitignore

This file was deleted.

1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
21 changes: 21 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
'name': 'Real estate',
'version': '0.1',
'depends': ['base'],
'author': 'odoo SA',
'category': 'Finance',
'description': """
Empty real estate app for tutorial purposes
""",
'application': 'True',
'data': [
'security/ir.model.access.csv',
'view/estate_property_views.xml',
'view/estate_property_type_views.xml',
'view/estate_property_tag_views.xml',
'view/estate_property_offer_views.xml',
'view/estate_menus.xml',
'view/user_property_views.xml'
],
'license': 'LGPL-3',
}
1 change: 1 addition & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property, estate_property_tag, estate_property_type, estate_property_offer, user_property
99 changes: 99 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.exceptions import UserError


class Estate(models.Model):
_name = 'estate.property'
_description = 'It allows to manage your properties'
_order = 'id desc'

name = fields.Char(required=True, default='Unknown')
property_type_id = fields.Many2one('estate.property.type')
tag_ids = fields.Many2many('estate.property.tag')
last_seen = fields.Datetime('Last Seen', default=fields.Datetime.now)
description = fields.Char(required=True)
postcode = fields.Char()
date_availability = fields.Date(default=fields.Date.today() + relativedelta(months=3), copy=False)
active = fields.Boolean(default=True)
expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
best_offer = fields.Float(compute='_compute_best_offer')
sales_person_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user)
offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer')
buyer_id = fields.Many2one('res.partner', string='Customer')
bedrooms = fields.Integer(default=2)
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_orientation = fields.Selection(
string='Orientation',
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')],
)
living_area = fields.Integer()
garden_area = fields.Integer()
total_area = fields.Float(compute='_compute_total_area')
state = fields.Selection(
string='State',
selection=[
('new', 'New'),
('offer received', 'Offer Received'),
('offer accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
],
readonly=True,
default='new',
)

_sql_constraints = [
('expected_price', 'CHECK(expected_price >= 0 )', 'A price should always be possitive'),
('selling_price', 'CHECK(selling_price >= 0 )', 'A price should always be possitive'),
]

@api.depends('living_area', 'garden_area')
def _compute_total_area(self):
for record in self:
record.total_area = record.living_area + record.garden_area

@api.onchange('offer_ids')
def _onchange_offer_ids(self):
if self.state == 'new':
if len(self.offer_ids):
self.state = 'offer received'
self._origin.state = 'offer received' # error, didn't found the source, fix, I'm a bugfixer

@api.depends('offer_ids')
def _compute_best_offer(self):
for record in self:
all_price = record.offer_ids.mapped('price')
if all_price:
record.best_offer = max(all_price)
else:
record.best_offer = 0

@api.onchange('garden')
def _onchange_garden(self):
if self.garden:
self.garden_area = 10
self.garden_orientation = 'north'
else:
self.garden_area = 0
self.garden_orientation = ''

def action_property_sold(self):
for record in self:
if record.state == 'cancelled':
raise UserError(self.env._('property already sold'))
record.state = 'sold'

def action_property_cancelled(self):
for record in self:
if record.state == 'sold':
raise UserError(self.env._('property already sold'))
record.state = 'cancelled'

@api.ondelete(at_uninstall=False)
def _unlink_for_state(self):
if self.state not in ['new', 'cancelled']:
raise UserError('You cannot delete an property that as is not New or cancelled')
67 changes: 67 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models
from odoo.exceptions import UserError


class EstateOffer(models.Model):
_name = 'estate.property.offer'
_description = 'It allows to create a new property offer'
_order = 'price desc'

price = fields.Float(required=True)
validity = fields.Integer()
date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date')
create_date = fields.Date(default=fields.Date.today(), required=True)
partner_id = fields.Many2one('res.partner', string='Customer')
property_id = fields.Many2one('estate.property', string='Property')
status = fields.Selection(
string='State',
selection=[
('pending', 'Pending'),
('accepted', 'Accepted'),
('refused', 'Refused'),
],
default='pending',
)
property_type_id = fields.Many2one(related='property_id.property_type_id')

_sql_constraints = [('price', 'CHECK(price >= 0 )', 'A price should always be possitive')]

@api.depends('create_date', 'validity')
def _compute_date_deadline(self):
for record in self:
record.date_deadline = (record.create_date or fields.Date.today()) + relativedelta(days=record.validity)

def _inverse_date(self):
for record in self:
record.validity = (record.date_deadline - fields.Date.today()).days

def action_accept_offer(self):
for record in self:
if record.property_id.state not in ['sold', 'cancelled']:
for offer in record.property_id.offer_ids:
offer.status = 'refused'
record.status = 'accepted'
record.property_id.selling_price = record.price
record.property_id.state = 'offer accepted'
else:
raise UserError(self.env._('property already sold'))
return True

def action_refuse_offer(self):
for record in self:
if record.property_id.state not in ['sold', 'cancelled']:
if record.status == 'accepted':
record.property_id.selling_price = 0
record.property_id.state = 'offer received'
else:
raise UserError(self.env._('property already sold'))
record.status = 'refused'
return True

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals['price'] < self.env['estate.property'].browse(vals['property_id']).best_offer:
raise UserError('The price is lower than the best-offer')
return super().create(vals_list)
12 changes: 12 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class EstateTags(models.Model):
_name = 'estate.property.tag'
_description = 'It allows to create a new property tag'
_order = 'name desc'

name = fields.Char(required=True)
color = fields.Integer(default=0)

_sql_constraints = [('unique_tag_by_estate_property', 'UNIQUE(name)', 'Only one name by tag')]
18 changes: 18 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from odoo import api, fields, models


class EstateType(models.Model):
_name = 'estate.property.type'
_description = 'It allows to create a new property type'
_order = 'sequence desc'

name = fields.Char(required=True)
sequence = fields.Integer('Sequence', default=1, help='Used to order properties types. Lower is better.')
properties_ids = fields.One2many('estate.property', 'property_type_id')
offer_ids = fields.One2many('estate.property.offer', 'property_type_id')
offer_count = fields.Integer(compute='_compute_offer_stats')

@api.depends('offer_ids')
def _compute_offer_stats(self):
for record in self:
record.offer_count = len(record.offer_ids) or 0
9 changes: 9 additions & 0 deletions estate/models/user_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from odoo import fields, models


class UserProperty(models.Model):
_inherit = 'res.users'

property_ids = fields.One2many(
'estate.property', 'sales_person_id', domain=[('state', 'in', ['new', 'offer received'])]
)
5 changes: 5 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1
estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1
estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1
estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1
12 changes: 12 additions & 0 deletions estate/view/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<odoo>
<menuitem id='test_menu_root' name='Estate'>
<menuitem id='test_first_level_menu' name='First Level'>
<menuitem id='test_model_menu_action' action='action_test_action'/>
</menuitem>
<menuitem id='property_type_menu' name='Settings'>
<menuitem id='property_type_menu_action' action='estate_property_type_action'/>
<menuitem id='property_tag_menu_action' action='estate_property_tag_action'/>
<menuitem id='property_offer_menu_action' action='estate_property_offer_action'/>
</menuitem>
</menuitem>
</odoo>
Loading