Skip to content
Draft
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
132 changes: 132 additions & 0 deletions real_estate/models/real_estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
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"
_order = "id desc"

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.filtered(lambda r: r.state == 'cancelled'):
raise UserError(_("Cancelled Properties Can Not be sold"))
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.filtered(lambda r: r.state == 'sold'):
raise UserError(_("Sold Properties can not be cancel"))
record.state = 'cancelled'
return True

def action_approve(self):
for record in self:
if record.filtered(lambda r: r.state == 'sold'):
raise UserError(_("Sold properties cannot accept other Offers."))
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"))
58 changes: 58 additions & 0 deletions real_estate/models/real_estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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"
_order = "price desc"

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_partner_id = models.Constraint('UNIQUE(partner_id, property_id)', "This User have already made an offer on this property")
11 changes: 11 additions & 0 deletions real_estate/models/real_estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields, models


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

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


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

name = fields.Char(required=True)
property_ids = fields.One2many('estate.property', 'property_type_id')
sequence = fields.Integer(default=1)
_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>
21 changes: 21 additions & 0 deletions real_estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?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" />
<field name="color" widget="color_picker"/>
</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>
46 changes: 46 additions & 0 deletions real_estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?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" editable="bottom">
<field name="sequence" widget="handle" />
<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>
<group>
<field name="property_ids">
<list>
<field name="name" />
<field name="expected_price" />
<field name="state" />
</list>
</field>
</group>
</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