Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
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>