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
3 changes: 3 additions & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from . import models

__all__ = ["models"]
16 changes: 16 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Estate",
"depends": ["base"],
"author": "Sreedev Kodichath <srkod@odoo.com>",
"license": "LGPL-3",
"data": [
# datafiles
"security/ir.model.access.csv",
# views
"views/estate_property.xml",
"views/estate_property_type.xml",
"views/estate_property_tag.xml",
"views/estate_property_offer.xml",
"views/estate_menus.xml",
],
}
13 changes: 13 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from . import (
estate_property,
estate_property_offer,
estate_property_tag,
estate_property_type,
)

__all__ = [
"estate_property",
"estate_property_offer",
"estate_property_tag",
"estate_property_type",
]
117 changes: 117 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from odoo.tools.float_utils import float_compare, float_is_zero
from odoo import api, models, fields, _
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError, ValidationError


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_recd", "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)
_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)", "Expected price should be positive."
)
selling_price = fields.Float(readonly=True, copy=False)
_check_selling_price = models.Constraint(
"CHECK(selling_price > 0)", "Selling price must be positive."
)

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",
)

@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:
if not float_is_zero(property.selling_price, precision_digits=2) and (
float_compare(
property.selling_price,
0.9 * property.expected_price,
precision_digits=2,
)
== -1
):
raise ValidationError(
_("Selling price is not atleast 90% of expected price.")
)
66 changes: 66 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from odoo import api, models, fields, _
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError


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

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

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"
)

@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(
pstat == "accepted"
for pstat in offer.property_id.offer_ids.mapped("status")
)
if is_not_actionable:
raise UserError(
_("An offer has already been accepted for this property.")
)
else:
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):
for offer in self:
offer.status = "refused"
return True

@api.depends("create_date", "date_deadline")
def _compute_validity(self):
for offer in self:
offer.validity = (
offer.date_deadline - (offer.create_date or fields.Date.today()).date()
).days
15 changes: 15 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import models, fields


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.",
)
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 models, fields


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

active = fields.Boolean(default=True)
name = fields.Char(required=True)
sequence = fields.Integer(string="Sequence", default=1, help="Used for ordering")
_name_uniq = models.Constraint(
"unique (name)",
"Each property type name must be unique.",
)
property_ids = fields.One2many(
"estate.property", "property_type_id", string="Properties"
)
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>
115 changes: 115 additions & 0 deletions estate/views/estate_property.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?xml version="1.0"?>
<odoo>
<record id="estate.estate_property_view_list" model="ir.ui.view">
<field name="name">estate.property.list</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<list string="Properties"
decoration-success="state == 'offer_recd' or state == 'offer_accepted'"
decoration-bf="state == 'offer_accepted'"
decoration-muted="state == 'sold'">
<field name="name" string="Name" />
<field name="postcode" string="Postcode" />
<field name="property_tag_ids" string="Tags" widget="many2many_tags" />
<field name="bedrooms" string="Bedrooms" />
<field name="living_area" string="Living Area (sqm)" />
<field name="expected_price" string="Expected Price" />
<field name="selling_price" string="Selling Price" />
<field name="date_availability" string="Available From" optional="hidden" />
</list>
</field>
</record>
<record id="estate.estate_property_view_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form>
<header>
<group invisible="state == 'sold' or state == 'cancelled'">
<button name="action_cancel_property" string="Cancel" type="object" icon="fa-trash" />
<button name="action_sold_property" string="Sold" type="object" icon="fa-check" />
</group>
<field name="state" widget="statusbar" statusbar_visible="new,offer_recd,offer_accepted,sold" />
</header>
<group>
<group>
<field name="name" />
<field name="property_tag_ids" string="Tags" widget="many2many_tags" options="{'color_field': 'color'}" />
</group>
</group>
<group>
<group>
<field name="postcode" string="Postcode" />
<field name="date_availability" string="Available From" />
</group>
<group>
<field name="expected_price" string="Expected Price" />
<field name="selling_price" string="Selling Price" />
<field name="best_price" string="Best Price" />
</group>
</group>
<notebook>
<page string="Description">
<group>
<group>
<field name="description" string="Description" />
<field name="garage" string="Garage" />
<field name="garden" string="Garden" />
<field name="garden_area" string="Garden Area (sqm)" invisible="not garden"/>
<field name="garden_orientation" string="Garden Orientation" invisible="not garden"/>
</group>
<group>
<group>
<field name="bedrooms" string="Bedrooms" />
<field name="living_area" string="Living Area (sqm)" />
<field name="facades" string="Facades" />
<field name="total_area" string="Total Area" />
</group>
</group>
</group>
<group>
<field name="property_type_id" string="Property Type" options="{'no_create': true}" />
<field name="seller_id" string="Sales Person" />
<field name="buyer_id" string="Buyer" />
</group>
</page>
<page string="Offers">
<group>
<field
name="offer_ids"
context="{'list_view_of': 'estate.estate_property_offer_list_form'}"
readonly="state == 'offer_accepted' or state == 'sold' or state == 'cancelled'" />
</group>
</page>
</notebook>
</form>
</field>
</record>

<record id="estate.estate_property_view_search" model="ir.ui.view">
<field name="name">estate.property.search</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<search string="Search">
<field name="name" string="Title" />
<field name="postcode" string="Postcode" />
<field name="expected_price" string="Expected Price" />
<field name="bedrooms" string="Bedrooms" />
<field name="living_area" string="Living Area (sqm)" filter_domain="[('living_area', '&gt;=', self)]" />
<field name="facades" string="Facades" />
<separator/>
<filter name="available" string="Available" domain="['|', ('state', '=', 'new'), ('state', '=', 'offer_recd')]"/>
<group>
<filter name="group_by_postcode" string="Group by Postcode" context="{'group_by':'postcode'}" />
</group>
</search>
</field>
</record>

<record id="estate.estate_property_list_form" model="ir.actions.act_window">
<field name="name">Estate Properties</field>
<field name="res_model">estate.property</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_available': True}</field>
</record>
</odoo>
Loading