Skip to content
1 change: 1 addition & 0 deletions real_estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
27 changes: 27 additions & 0 deletions real_estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
'name': 'Real estate',
'version': '0.1.0',
'summary': 'Manage real estate properties',
'sequence': '1',
'description': """
Buying & selling Properties
===========================
This Module provide functionalities from where you manage the real estate properties from finding buyer to get best price.
""",
'category': 'sales',
'website': 'https://www.ishw.tech',
'depends': [
'base'
],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_menus.xml',
],
'installable': True,
'application': True,
'author': 'Ishwar',
'license': 'LGPL-3',
}
4 changes: 4 additions & 0 deletions real_estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import real_estate_property
from . import real_estate_property_type
from . import real_estate_property_tag
from . import real_estate_property_offer
131 changes: 131 additions & 0 deletions real_estate/models/real_estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from dateutil.relativedelta import relativedelta

from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why postcode is char() field?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used Char because postcodes in many countries contain letters (e.g., Canada) .An Integer field would not be able to handle this.

date_availability = fields.Date(
copy=False,
default=lambda self: fields.Date.today() + relativedelta(months=3)
)
expected_price = fields.Float(required=True)
selling_price = fields.Float(
readonly=True,
copy=False,
)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
selection=[
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West'),
],
help='Garden facing direction',
)
active = fields.Boolean(default=True)
state = fields.Selection(
string='States',
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
],
required=True,
copy=False,
default='new',
)
property_type_id = fields.Many2one("estate.property.type")
buyer_id = fields.Many2one("res.partner", copy=False)
salesperson_id = fields.Many2one(
"res.users", string="Sales Person", default=lambda self: self.env.user
)
tag_ids = fields.Many2many("estate.property.tag")
offer_ids = fields.One2many("estate.property.offer", "property_id")
total_area = fields.Float(compute="_compute_total_area")
best_price = fields.Float(compute="_compute_best_price")

@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.depends('offer_ids.price')
def _compute_best_price(self):
for record in self:
if not record.mapped("offer_ids.price"):
record.best_price = 0
else:
record.best_price = max(record.mapped("offer_ids.price"))

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

def action_sold(self):
for record in self:
if record.state == 'cancelled':
raise UserError("Cancelled Properties Can Not be sold")
Comment on lines +87 to +89

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use filtered here, and it is good to make the error message translatable.

if not record.buyer_id:
raise UserError("Can not sold property who has no buyer")
record.state = 'sold'
return True

def action_cancelled(self):
for record in self:
if record.state == 'sold':
raise UserError("Sold Properties can not be cancel")
Comment on lines +96 to +98

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use filtered here, and it is good to make the error message translatable.

record.state = 'cancelled'
return True

def action_approve(self):
for record in self:
if record.state == 'sold':
raise UserError("Sold properties cannot accept other Offers.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is good to make the error message translatable.

target_offer = record.offer_ids.filtered(lambda r: r.price == record.best_price)
if target_offer:
target_offer = target_offer[0]
target_offer.status = 'accepted'
(record.offer_ids - target_offer).status = 'refused'
record.selling_price = target_offer.price
record.buyer_id = target_offer.partner_id
record.state = 'offer_accepted'

@api.constrains('expected_price', 'selling_price')
def _check_price(self):
for record in self:
if record.expected_price <= 0:
raise ValidationError("Expected Price Must be Positive")
if record.selling_price < 0:
raise ValidationError("Selling price Must be Positive")

@api.constrains('selling_price', 'expected_price')
def _check_expected_selling_price(self):
for record in self:
if float_is_zero(record.selling_price, precision_digits=2):
continue

expected_selling_price = record.expected_price * 0.9
if float_compare(record.selling_price, expected_selling_price, precision_digits=2) < 0:
raise ValidationError("Selling price Must be 90% of the expected price")
57 changes: 57 additions & 0 deletions real_estate/models/real_estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from dateutil.relativedelta import relativedelta

from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Offers on Buy or Sell for properties"

price = fields.Float()
status = fields.Selection(
[
("refused", "Refused"),
("accepted", "Accepted"),
],
copy=False,
)
partner_id = fields.Many2one("res.partner", required=True)
property_id = fields.Many2one("estate.property", required=True)
validity = fields.Integer(default=7)
date_deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline")

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

def _inverse_deadline(self):
for record in self:
start_date = record.create_date or fields.Date.today()
record.validity = (record.date_deadline - fields.Date.to_date(start_date)).days

def action_accept(self):
for record in self:
if record.property_id.buyer_id:
raise UserError("You can accept offer only once per property")
record.status = 'accepted'
record.property_id.buyer_id = record.partner_id
record.property_id.selling_price = record.price
(record.property_id.offer_ids - record).status = 'refused'
record.property_id.state = 'offer_accepted'
return True

def action_refuse(self):
for record in self:
record.status = 'refused'
return True

@api.constrains('price')
def _check_offer_price(self):
for record in self:
if record.price <= 0:
raise ValidationError("Offer Price Must be Positive")

_check_partner = models.Constraint('UNIQUE(partner_id, property_id)', "User have already made a offer on this property")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow the coding guidelines.

10 changes: 10 additions & 0 deletions real_estate/models/real_estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'This are tags used to identify property'

name = fields.Char(required=True)

_check_tag_name = models.Constraint('UNIQUE(name)', "Tag name Must be unique")
10 changes: 10 additions & 0 deletions real_estate/models/real_estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import fields, models


class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Real Estate Property Types"

name = fields.Char(required=True)

_check_type_name = models.Constraint('UNIQUE(name)', "Type must be Unique")
5 changes: 5 additions & 0 deletions real_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
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1
35 changes: 35 additions & 0 deletions real_estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<menuitem
id="estate_property_menu_root"
name="Real Estate"/>
<menuitem
id="estate_property_menu_1"
name="Advertisements"
parent="estate_property_menu_root"
sequence="1"/>
<menuitem
id="estate_property_menu_action"
name="All Properties"
parent="estate_property_menu_1"
action="estate_property_action"/>
<menuitem
id="estate_property_menu_2"
name="settings"
parent="estate_property_menu_root"
sequence="2"/>
<menuitem
id="estate_property_type_menu_action"
name="Property Type"
parent="estate_property_menu_2"
sequence="1"
action="estate_property_type_action"/>
<menuitem
id="estate_property_tag_menu_action"
name="Property Tag"
parent="estate_property_menu_2"
sequence="2"
action="estate_property_tag_action"/>
</data>
</odoo>
20 changes: 20 additions & 0 deletions real_estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="estate_property_tag_view_list" model="ir.ui.view">
<field name="name">estate.property.tag.view.list</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<list string="Property Tags" editable="bottom">
<field name="name" />
</list>
</field>
</record>

<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list,form</field>
</record>
</data>
</odoo>
36 changes: 36 additions & 0 deletions real_estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<record id="estate_property_type_view_list" model="ir.ui.view">
<field name="name">estate.property.type.view.list</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<list string="Property Types">
<field name="name" string="Property Type" />
</list>
</field>
</record>

<record id="estate_property_type_view_form" model="ir.ui.view">
<field name="name">estate.property.type.view.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form>
<sheet>
<div class="oe_title">
<h1>
<field name="name" placeholder="e.g. House, Apartment" />
</h1>
</div>
</sheet>
</form>
</field>
</record>

<record id="estate_property_type_action" model="ir.actions.act_window">
<field name="name">Property Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list,form</field>
</record>
</data>
</odoo>
Loading