Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
4f2a153
[ADD] estate: create estate application
coleo-odoo Sep 15, 2026
d0e571a
[IMP] estate: create a table property with name and description
coleo-odoo Sep 15, 2026
9d3cf5b
[IMP] estate: create columns to the property table
coleo-odoo Sep 15, 2026
29a8dc5
[IMP] estate: set the column name and selling_price to not null
coleo-odoo Sep 15, 2026
02a48ac
[CLN] estate: add a newline at the end of files
coleo-odoo Sep 15, 2026
c4b618c
[IMP] estate: create access rights for users
coleo-odoo Sep 15, 2026
4a114ec
[IMP] estate: create a basic action view for property
coleo-odoo Sep 15, 2026
b7e9a51
[IMP] estate: applied different suggestions to improve code
coleo-odoo Sep 15, 2026
8c53531
[IMP] estate: applied different suggestions to improve code
coleo-odoo Sep 15, 2026
9e1e2a7
[IMP] estate: created a menu, fields, attributes and view
coleo-odoo Sep 15, 2026
a8cc8f4
[IMP] estate: create custom views for the list and form of the proper…
coleo-odoo Sep 16, 2026
f3b96af
[IMP] estate: create tag, type and offer with their menu action,list…
coleo-odoo Sep 17, 2026
8d16b99
[IMP] estate: create multiple computed fields for offer and property
coleo-odoo Sep 17, 2026
98ccc2f
[FIX] estate: fix a bug when there is no offer for a property
coleo-odoo Sep 17, 2026
1dbfe02
[IMP] estate: create multiple action button to update states of offer…
coleo-odoo Sep 17, 2026
104ffa4
[FIX] estate: fix warning onchange about erasing the fields Garden Ar…
coleo-odoo Sep 17, 2026
b0f493b
[IMP] estate: add constraint on prices fields to avoid negative numbe…
coleo-odoo Sep 17, 2026
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
15 changes: 15 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
'name': 'Real Estate',
'author': 'Odoo S.A.',
'license': 'LGPL-3',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_menu.xml',
],
'application': True,
}
4 changes: 4 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
129 changes: 129 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare, float_is_zero


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

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False,
default=lambda self: fields.Date.add(
fields.Date.today(),
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()
has_garage = fields.Boolean()
has_garden = fields.Boolean()
garden_area = fields.Integer()
total_area = fields.Integer(compute="_compute_total_area")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
)
active = fields.Boolean(default=True)
state = fields.Selection(
string="State",
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
default="new",
required=True,
copy=False,
)
type_id = fields.Many2one("estate.property.type")
partner_id = fields.Many2one(
"res.partner",
string="Buyer",
index=True,
copy=False,
)
user_id = fields.Many2one(
"res.users",
string="Salesperson",
index=True,
default=lambda self: self.env.user,
)
tag_ids = fields.Many2many("estate.property.tag")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
best_price = fields.Float(compute="_compute_best_price")

_check_expected_price_positive = models.Constraint(
"CHECK(expected_price >= 0)",
"The Expected Price should be positive.",
)
Comment thread
msho-odoo marked this conversation as resolved.
_check_selling_price_positive = models.Constraint(
"CHECK(selling_price >= 0)",
"The Selling Price should be positive.",
)

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

@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")
def _compute_best_price(self):
for record in self:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped("price"))
else:
record.best_price = 0

@api.onchange("has_garden")
def _onchange_hjas_garden(self):
if self.has_garden:
self.garden_area = 10
self.garden_orientation = "north"
else:
self.garden_area = 0
self.garden_orientation = False

def cancel_property(self):
if self.state == "sold":
msg = "A sold property cannot be cancelled"
raise UserError(msg)
self.state = "cancelled"
return True

def sell_property(self):
if self.state == "cancelled":
msg = "A cancelled property cannot be sold"
raise UserError(msg)
if not ("accepted" in self.offer_ids.mapped("status")):
msg = "A offer need to be accepted before selling"
raise UserError(msg)
self.state = "sold"
return True
65 changes: 65 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from odoo import api, fields, models
from odoo.exceptions import UserError


class Offer(models.Model):
_name = "estate.property.offer"
_description = "Offer for a property of Estate"

price = fields.Float(required=True)
status = fields.Selection(
selection=[
("accepted", "Accepted"),
("refused", "Refused"),
],
copy=False,
)
partner_id = fields.Many2one(
"res.partner",
string="Partner",
index=True,
required=True,
)
property_id = fields.Many2one("estate.property", required=True)
validity = fields.Integer(default=7)
date_deadline = fields.Date(
compute="_compute_deadline",
inverse="_inverse_deadline",
)

_check_price_positive = models.Constraint(
"CHECK(price >= 0)",
"The Price of an Offer should be positive.",
)

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

def _inverse_deadline(self):
for record in self:
record.validity = (
record.date_deadline - fields.Date.to_date(record.create_date)
).days

def confirm_offer(self):
if "accepted" in self.property_id.offer_ids.mapped("status"):
msg = "Property already sold"
raise UserError(msg)
self.status = "accepted"
self.property_id.selling_price = self.price
return True

def refuse_offer(self):
if self.status == "accepted":
self.property_id.selling_price = 0
self.status = "refused"
return True
13 changes: 13 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import fields, models


class Type(models.Model):
_name = "estate.property.tag"
_description = "Tag of property of Estate"

name = fields.Char(required=True)

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


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

name = fields.Char(required=True)

_uniq_name = models.Constraint(
"unique(name)",
"A Type name should be unique.",
)
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_user","access_estate_property","model_estate_property","base.group_user",1,1,1,1
"access_estate_property_type_user","access_estate_property_type","model_estate_property_type","base.group_user",1,1,1,1
"access_estate_property_tag_user","access_estate_property_tag","model_estate_property_tag","base.group_user",1,1,1,1
"access_estate_property_offer_user","access_estate_property_offer","model_estate_property_offer","base.group_user",1,1,1,1
11 changes: 11 additions & 0 deletions estate/views/estate_menu.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_first_level_menu_advertisements" name="Advertisements">
<menuitem id="estate_menu_action_property_list" action="estate_property_show_list" name="Properties"/>
</menuitem>
<menuitem id="estate_first_level_menu_settings" name="Settings">
<menuitem id="estate_menu_action_property_type_list" action="estate_property_type_show_list" name="Property Types"/>
<menuitem id="estate_menu_action_property_tag_list" action="estate_property_tag_show_list" name="Property Tags"/>
</menuitem>
</menuitem>
</odoo>
32 changes: 32 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<odoo>
<record id="estate_property_view_offer_tree" 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 string="Offers">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="confirm_offer" string="Confirm" type="object" icon="fa-check"/>
<button name="refuse_offer" string="Refuse" type="object" icon="fa-times"/>
<field name="status"/>
</list>
</field>
</record>

<record id="estate_property_offer_view_form" 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="Offer">
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
</group>
</form>
</field>
</record>
</odoo>
7 changes: 7 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<odoo>
<record id="estate_property_tag_show_list" model="ir.actions.act_window">
<field name="name">Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list</field>
</record>
</odoo>
7 changes: 7 additions & 0 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<odoo>
<record id="estate_property_type_show_list" model="ir.actions.act_window">
<field name="name">Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list</field>
</record>
</odoo>
Loading