Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
14 changes: 14 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "Estate",
"depends": ["base"],
"author": "Odoo S.A.",
"license": "LGPL-3",
"data": [
"security/ir.model.access.csv",
"views/estate_property.xml",
"views/estate_property_type.xml",
"views/estate_property_tag.xml",
"views/estate_property_offer.xml",
"views/estate_menus.xml",
],
}
6 changes: 6 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import (
estate_property,
estate_property_offer,
estate_property_tag,
estate_property_type,
)
132 changes: 132 additions & 0 deletions estate/models/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
Comment thread
sreedevk marked this conversation as resolved.
from odoo.tools.float_utils import float_compare, float_is_zero


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

active = fields.Boolean(default=True)
state = fields.Selection(
string="State",
copy=False,
default="new",
required=True,
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
)
name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
default=(fields.Date.today() + relativedelta(months=3)),
copy=False,
)
seller_id = fields.Many2one(
"res.users",
string="Sales Person",
default=lambda self: self.env.user,
)
buyer_id = fields.Many2one("res.partner", string="Buyer")

expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
best_price = fields.Float(compute="_compute_best_price")
property_tag_ids = fields.Many2many("estate.property.tag", string="Property Tags")
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer("Property Garden Area")
total_area = fields.Integer(compute="_compute_total_area")
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
help="Select garden orientation",
)

_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
"Expected price should be positive.",
)
_check_selling_price = models.Constraint(
"CHECK(selling_price > 0)",
"Selling price must be positive.",
)

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

@api.depends("offer_ids.price")
def _compute_best_price(self):
for property in self:
property.best_price = max(property.offer_ids.mapped("price"), default=0)

@api.onchange("garden")
def _onchange_garden(self):
for property in self:
if property.garden:
property.garden_area = 10
property.garden_orientation = "north"
else:
property.garden_area = None
property.garden_orientation = None

def action_cancel_property(self):
for property in self:
if property.state == "sold":
raise UserError(_("Sold properties cannot be cancelled."))
else:
property.state = "cancelled"
return True

def action_sold_property(self):
for property in self:
if property.state == "cancelled":
raise UserError(_("Cancelled properties cannot be sold."))
else:
property.state = "sold"
return True

@api.constrains("selling_price")
def _check_selling_price(self):
for property in self:
selling_price = property.selling_price
expected_price = property.expected_price
if (
not float_is_zero(property.selling_price, precision_digits=2)
and float_compare(
selling_price,
0.9 * expected_price,
precision_digits=2,
)
== -1
):
raise ValidationError(
_("Selling price is not atleast 90% of expected price."),
)

@api.ondelete(at_uninstall=False)
def _ensure_state_before_deletion(self):
for property in self:
if property.state in ("new", "cancelled"):
raise UserError(_("can't delete a properties in intermediate states."))
76 changes: 76 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from dateutil.relativedelta import relativedelta

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


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Offer made on properties"
_order = "price desc"

property_type_id = fields.Many2one(related="property_id.property_type_id")
price = fields.Float()
status = fields.Selection(
string="status",
selection=[
("accepted", "Accepted"),
("refused", "Refused"),
],
help="Offer Status",
)
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_date_deadline",
inverse="_compute_validity",
)

_check_price = models.Constraint(
"CHECK(price > 0)",
"Offer price must be positive.",
)

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

def action_accept_offer(self):
for offer in self:
is_not_actionable = any(
status == "accepted"
for status in offer.property_id.offer_ids.mapped("status")
)
if is_not_actionable:
raise UserError(
_("An offer has already been accepted for this property."),
)

offer.status = "accepted"
offer.property_id.buyer_id = offer.partner_id
offer.property_id.selling_price = offer.price
offer.property_id.state = "sold"
return True

def action_reject_offer(self):
self.status = "refused"
return True

@api.depends("create_date", "date_deadline")
def _compute_validity(self):
for offer in self:
self.validity = (
offer.date_deadline - (offer.create_date or fields.Date.today()).date()
).days

@api.model
def create(self, vals):
property = self.env["estate.property"].browse(vals["property_id"])
if property.state == "new":
property.state = "offer_recevied"

return super(EstatePropertyOffer, self).create(vals)
16 changes: 16 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Tag representing an attribute's presence in a property"
_order = "name"

active = fields.Boolean(default=True)
color = fields.Integer(string="Color")
name = fields.Char(required=True)

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


class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "type of the estate property"
_order = "name"

offer_ids = fields.One2many(
"estate.property.offer",
"property_type_id",
string="Offers",
)
offer_count = fields.Integer(compute="_compute_offer_count")
active = fields.Boolean(default=True)
name = fields.Char(required=True)
sequence = fields.Integer(string="Sequence", default=1, help="Used for ordering")
property_ids = fields.One2many(
"estate.property",
"property_type_id",
string="Properties",
)

_name_uniq = models.Constraint(
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

after all the fields

"unique (name)",
"Each property type name must be unique.",
)

@api.depends("offer_ids")
def _compute_offer_count(self):
self.offer_count = len(property.offer_ids)
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
estate.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,model_estate_property_type,base.group_user,1,1,1,1
estate.access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1
estate.access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1
21 changes: 21 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate.app_menu" name="Estate">
<menuitem id="advertisements_list_menu" name="Advertisements">
<menuitem
id="estate.advertisements_properties_list_menu_item"
name="Properties"
action="estate.estate_property_list_form"/>
</menuitem>
<menuitem id="estate.settings_list_menu" name="Settings">
<menuitem
id="estate.advertisements_property_types_list_menu_item"
name="Property Types"
action="estate.estate_property_type_list_form"/>
<menuitem
id="estate.advertisements_property_tag_list_menu_item"
name="Property Tags"
action="estate.estate_property_tag_list_form"/>
</menuitem>
</menuitem>
</odoo>
Loading