Skip to content
Open
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
13 changes: 13 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"name": "Real estate",
"depends": ["base"],
"application": True,
"installable": True,
"data": [
"security/ir.model.access.csv",
"views/estate_property_views.xml",
"views/estate_menus.xml",
],
"license": "LGPL-3",
"author": "Abdallah",
}
1 change: 1 addition & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property, estate_property_misc
113 changes: 113 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from odoo import fields, models, api, exceptions


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

name = fields.Char(required=True, translate=True, string="Title")
description = fields.Text(translate=True)
postcode = fields.Char()
date_availability = fields.Date(
copy=False,
default=fields.Datetime.add(fields.Datetime.today(), months=3),
string="Available From",
)
expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer(string="Living Area(sqm)")
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer(string="Garden Area(sqm)")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
("north", "North"),
("east", "East"),
("west", "West"),
("south", "South"),
],
)
active = fields.Boolean(default=True)
status = fields.Selection(
required=True,
copy=False,
default="new",
selection=[
("new", "New"),
("offer_recieved", "Offer Recieved"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
)

property_type_id = fields.Many2one("estate.property.type", string="Property Type")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy="False")
salesperson_id = fields.Many2one(
"res.users",
string="Salesman",
default=lambda self: self.env.user,
)

tag_ids = fields.Many2many("estate.property.tags", string="Tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")

total_area = fields.Float(
compute="_compute_total_area",
string="Total Area(sqm)",
)
best_offer = fields.Float(
compute="_compute_best_price",
string="Best Offer",
)

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

@api.depends("offer_ids")
def _compute_best_price(self):
for record in self:
if record.offer_ids:
record.best_offer = max(record.offer_ids.mapped("price"))
else:
record.best_offer = 0

@api.onchange("garden")
def _onchange_garden(self):
self.garden_area = 10 if self.garden else 0
self.garden_orientation = "north" if self.garden else ""

def sell_property_action(self):
for record in self:
if record.status == "cancelled":
raise exceptions.UserError("Property Cancelled")
record.status = "sold"
return True

def cancel_property_action(self):
for record in self:
if record.status == "sold":
raise exceptions.UserError("Property Sold")
record.status = "cancelled"
return True

_check_positive_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'Prices Must Be Positive',
)

_check_positive_selling_price = models.Constraint(
'CHECK(selling_price > 0)',
'Prices Must Be Positive',
)

@api.constrains("selling_price")
def _check_selling_price(self):
for record in self:
if (record.selling_price - 0.9 * record.expected_price >= 1e-5):
raise exceptions.ValidationError("The selling price is too low")
82 changes: 82 additions & 0 deletions estate/models/estate_property_misc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from odoo import fields, models, api, exceptions


class PropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate Property Type"

name = fields.Char(required=True)

_check_unique_name = models.Constraint(
"unique(name)",
"Type must be unique",
)


class PropertyTags(models.Model):
_name = "estate.property.tags"
_description = "Estate Property Tags"

name = fields.Char(required=True)

_check_unique_name = models.Constraint(
"unique(name)",
"Tag must be unique",
)


class PropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer"

price = fields.Float(required=True)
status = fields.Selection(
string="Status",
copy=False,
selection=[
("accepted", "Accepted"),
("refused", "Refused"),
],
)
buyer_id = fields.Many2one("res.partner", string="Buyer", required=True)
property_id = fields.Many2one("estate.property", string="Property", required=True)

validity = fields.Integer(default=7)
date_deadline = fields.Date(compute="_compute_date", inverse="_inverse_date")

@api.depends("validity")
def _compute_date(self):
for record in self:
create_date = record.create_date
if not create_date:
create_date = fields.Date.today()
record.date_deadline = fields.Date.add(
create_date,
days=record.validity,
)

def _inverse_date(self):
for record in self:
create_date = record.create_date.date()
if not create_date:
create_date = fields.Date.today()
record.validity = (record.date_deadline - create_date).days

def accept_offer(self):
if self.property_id.status == "sold":
raise exceptions.UserError("Property is already sold")
self.property_id.buyer_id = self.buyer_id
self.property_id.selling_price = self.price
self.status = "accepted"
return True

def refuse_offer(self):
if self.property_id.status == "sold":
raise exceptions.UserError("Property is already sold")
self.status = "refused"
return True

_check_positive_price = models.Constraint(
"CHECK(price > 0.001)",
"Prices Must Be Positive",
)
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_model,access_estate_model,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_tags,access_estate_property_tags,model_estate_property_tags,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
22 changes: 22 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate"/>
<menuitem id="estate_ads_menu"
name="Ads"
parent="estate_menu_root"/>
<menuitem id="estate_ads_properties"
name="Properties"
action="estate_property_action"
parent="estate_ads_menu"/>
<menuitem id="estate_settings_menu"
name="Settings"
parent="estate_menu_root"/>
<menuitem id="estate_settings_types"
name="Property Types"
action="estate_property_type_action"
parent="estate_settings_menu"/>
<menuitem id="estate_settings_tags"
name="Property Tags"
action="estate_property_tags_action"
parent="estate_settings_menu"/>
</odoo>
136 changes: 136 additions & 0 deletions estate/views/estate_property_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_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="Property">
<field name="name"/>
<field name="postcode"/>
<field name="expected_price"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<filter string="Available" name="available" domain="['|', ('status', '=', 'new'), ('status', '=', 'offer_received')]"/>
<filter string="Postcode" name="postcode" context="{'group_by':'postcode'}"/>
</search>
</field>
</record>
<record id="estate_property_form" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="Property Form">
<header>
<button name="sell_property_action" type="object" string="Mark Sold"/>
<button name="cancel_property_action" type="object" string="Cancel"/>
</header>
<sheet>
<group>
<h1>
<field name="name" placeholder="Property name"/>
</h1>
</group>
<group>
<field name="tag_ids" widget="many2many_tags"/>
</group>
<group>
<group>
<field name="status"/>
</group>
<group>
<field name="property_type_id"/>
</group>
<group>
<field name="postcode"/>
</group>
<group>
<field name="date_availability"/>
</group>
<group>
<field name="expected_price"/>
</group>
<group>
<field name="selling_price"/>
</group>
<group>
<field name="best_offer"/>
</group>
</group>
<group>
<notebook>
<page string="Description">
<group>
<field name="description"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<field name="garage"/>
<field name="garden"/>
<field name="garden_area"/>
<field name="garden_orientation"/>
<field name="total_area"/>
</group>
</page>
<page string="Offers">
<field name = "offer_ids">
<list>
<field name="price"/>
<field name="status"/>
<field name="buyer_id"/>
<field name="property_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="accept_offer" string="Accept" type="object" icon="fa-check"/>
<button name="refuse_offer" string="Refuse" type="object" icon="fa-times"/>
</list>
</field>
</page>
<page string="Other Info">
<group>
<field name="salesperson_id"/>
</group>
<group>
<field name="buyer_id"/>
</group>
</page>
</notebook>
</group>
</sheet>
</form>
</field>
</record>
<record id="estate_property_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="Property Info">
<field name="name" width="120px"/>
<field name="postcode" width="120px"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="expected_price"/>
<field name="selling_price"/>
<field name="date_availability"/>
<field name="property_type_id"/>
<field name="tag_ids" widget="many2many_tags"/>
</list>
</field>
</record>
<record id="estate_property_action" model="ir.actions.act_window">
<field name="name">properties</field>
<field name="res_model">estate.property</field>
<field name="view_mode">list,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>
<record id="estate_property_tags_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tags</field>
<field name="view_mode">list,form</field>
</record>
</odoo>