Skip to content
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
19 changes: 19 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': 'Real Estate',
'description': 'Welcome to my Real Estate',
'author': 'KRPAT',
'website': 'https://www.odoo.com/',
'category': 'Tutorials',
'version': '1.0',
'license': 'LGPL-3',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/estate_property_offer_views.xml',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_menus.xml',
'views/res_users_views.xml',
],
}
5 changes: 5 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
from . import res_users
145 changes: 145 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
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 = "Estate Property"
_order = "id desc"

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

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"),
],
string="Garden Orientation",
)
active = fields.Boolean(default=True)
state = fields.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", string="Property Type")
buyer_id = fields.Many2one(
"res.partner",
string="Buyer",
copy=False,
)
user_id = fields.Many2one(
"res.users", string="Salesman", default=lambda self: self.env.user
)
tag_ids = fields.Many2many(
"estate.property.tag",
string="Tags",
)
offer_ids = fields.One2many(
"estate.property.offer",
"property_id",
string="Offers",
)
salesperson_id = fields.Many2one(
"res.users", string="Salesperson", default=lambda self: self.env.user
)
total_area = fields.Float(
compute="_compute_total_area", string="Total Area", readonly=False
)
best_price = fields.Float(
compute="_compute_best_price", string="Best Offer", store=True
)
_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
"The expected price must be strictly positive",
)
_check_selling_price = models.Constraint(
"CHECK(selling_price >= 0)", "The selling price must be positive"
)

@api.ondelete(at_uninstall=False)
def _unlink_if_allowed(self):
for record in self:
if record.state not in ("new", "cancelled"):
raise ValidationError(
_("You can only delete properties that are New or Cancelled.")
)

@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:
record.best_price = max(record.offer_ids.mapped("price"), default=0.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 = False

def action_sold(self):
if self.filtered(lambda x: x.state == "cancelled"):
raise UserError(_("A cancelled Property cannot be sold."))
self.write({"state": "sold"})

def action_cancel(self):
if self.filtered(lambda x: x.state == "sold"):
raise UserError(_("A sold Property cannot be cancelled."))
self.write({"state": "cancelled"})

@api.constrains("expected_price", "selling_price")
def _check_selling_price(self):
for record in self:
if float_is_zero(record.selling_price, precision_rounding=0.01):
continue
min_price = record.expected_price * 0.9
if float_compare(record.selling_price, min_price, precision_rounding=0.01) < 0:
raise ValidationError("The selling price cannot be lower than 90% of the expected price.")

def accept_best_price(self):
for record in self:
if not record.offer_ids:
raise UserError("There are no offers to accept.")

Choose a reason for hiding this comment

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

unneccary space.

best_offer = max(record.offer_ids, key=lambda t: t.price)
record.offer_ids.write({"status": "refused"})
best_offer.write({"status": "accepted"})

record.selling_price = record.best_price
record.buyer_id = best_offer.partner_id
return True
104 changes: 104 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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 = "Estate Property Offer"
_order = "price desc"

price = fields.Float(string="Price")
status = fields.Selection(
[
("accepted", "Accepted"),
("refused", "Refused"),
],
string="Status",
copy=False,
)
partner_id = fields.Many2one(
"res.partner",
string="Partner",
required=True,
)
property_id = fields.Many2one(
"estate.property", string="Property", required=True, ondelete="cascade"
)
property_type_id = fields.Many2one(
"estate.property.type",
related="property_id.property_type_id",
store=True,
readonly=True,
)
validity = fields.Integer(default=7, string="Validity (days)")
date_deadline = fields.Date(
compute="_compute_date_deadline",
inverse="_inverse_date_deadline",
string="Deadline",
)

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

def _inverse_date_deadline(self):
for record in self:
if record.create_date and record.date_deadline:
record.validity = (
record.date_deadline - record.create_date.date()
).days

@api.model
def create(self, vals_list):
for vals in vals_list:
price = vals.get("price", 0.0)
property_id = vals.get("property_id")
property_rec = self.env["estate.property"].browse(property_id)
if not property_rec.exists():
raise ValidationError(_("Invalid Property."))

if property_rec.offer_ids:
max_offer = max(property_rec.offer_ids.mapped("price"))
if price <= max_offer:
raise ValidationError(
_("The offer must be higher than existing offers")
)

offers = super().create(vals_list)
for i in offers:
i.property_id.state = "offer_received"

return offers

def action_accept(self):
for record in self:
if record.property_id.offer_ids.filtered(lambda o: o.status == "accepted"):
raise UserError("Only one offer can be accepted")
record.status = "accepted"
record.property_id.write(
{
"buyer_id": record.partner_id,
"selling_price": record.price,
"state": "offer_accepted",
}
)
return True

def action_refuse(self):
self.write({"status": "refused"})
return True

_check_price = models.Constraint(
"CHECK(price > 0)", "The Offer price must be strictly positive"
)
14 changes: 14 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Estate Property Tag"
_order = "name"

name = fields.Char(string="Name", required=True)
color = fields.Integer()

_check_tag_name = models.Constraint(
"UNIQUE(name)", "The property tag name must be unique"
)
27 changes: 27 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from odoo import api, fields, models


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

name = fields.Char(required=True)
sequence = fields.Integer(default=10)
_check_type_name = models.Constraint(
"UNIQUE(name)", "The property type name must be unique"
)
property_ids = fields.One2many(
"estate.property",
"property_type_id",
string="Properties",
)
offer_ids = fields.One2many(
"estate.property.offer", "property_type_id", string="Offers"
)
offer_count = fields.Integer(compute="_compute_offer_count")

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


class ResUsers(models.Model):
_inherit = "res.users"

property_ids = fields.One2many(
"estate.property",
"salesperson_id",
string="Owned Properties",
domain=["|", ("state", "=", "new"), ("state", "=", "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
access_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1
11 changes: 11 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<menuitem id="estate_menu_root" name="Real Estate"/>
<menuitem id="estate_first_level_menu" name="Estate" parent="estate_menu_root"/>
<menuitem id="estate_property_menu_action" name="Properties" parent="estate_first_level_menu" action="estate_property_action"/>
<menuitem id="estate_settings_menu" name="Settings" parent="estate_menu_root"/>
<menuitem id="estate_property_type_menu_action" name="Property Types" parent="estate_settings_menu" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu_action" name="Property Tags" parent="estate_settings_menu" action="estate_property_tag_action"/>
</data>
</odoo>
43 changes: 43 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="estate_property_offer_action" model="ir.actions.act_window" >
<field name="name">Offers</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">list,form</field>
<field name="domain">[('property_type_id', '=', active_id)]</field>
</record>

<record id="estate_property_offer_list_view" model="ir.ui.view">
<field name="name">estate.property.offer.list</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<list editable="bottom" decoration-danger="status == 'refused'" decoration-success="status == 'accepted'">
<field name="price"/>
<field name="partner_id"/>
<field name="property_id"/>
<field name="property_type_id"/>
<field name="validity" string="Validity(days)"/>
<field name="date_deadline"/>
<field name="status"/>
</list>
</field>
</record>

<record id="estate_property_offer_form_view" model="ir.ui.view">
<field name="name">estate.property.offer.form</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form string="Create Offers">
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="status"/>
<field name="validity" string="Validity(days)"/>
<field name="date_deadline"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
Loading