Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b592011
[ADD] estate: Add real estate module scafolding
ollet-odoo Sep 15, 2026
9282f17
[IMP] estate: Add estate.property model
ollet-odoo Sep 15, 2026
339767a
[IMP] estate: add first access rule to estate_property
ollet-odoo Sep 15, 2026
433fc25
[IMP] estate: add Menu, form and list view
ollet-odoo Sep 15, 2026
7a55a96
[IMP] estate: Improve Property model
ollet-odoo Sep 15, 2026
703a614
[CLN] estate: Menu, form and list view
ollet-odoo Sep 15, 2026
5ec1f59
[IMP] estate: Add list view
ollet-odoo Sep 15, 2026
b365a35
[FIX] estate Property set active by default
ollet-odoo Sep 15, 2026
2dd8a20
[IMP] estate: Property do not duplicate state
ollet-odoo Sep 15, 2026
8badc9b
[IMP] estate: Property form
ollet-odoo Sep 15, 2026
18d1370
[CLN] estate: Correct review comments
ollet-odoo Sep 15, 2026
bdaa9ea
[IMPL] estate: display name and state in views
ollet-odoo Sep 15, 2026
077d633
[IMP] estate: add search, filter and group by on property
ollet-odoo Sep 15, 2026
7783008
[IMP] estate: add Type for properties
ollet-odoo Sep 16, 2026
1cda53b
[IMP] estate: add buyer and salesperson on Property
ollet-odoo Sep 16, 2026
cd5e4fd
[IMP] estate: Add Property tags
ollet-odoo Sep 16, 2026
c0879b8
[IMP] estate: Add Property Offers
ollet-odoo Sep 16, 2026
b587426
[IMP] estate: Add living_area on Property
ollet-odoo Sep 16, 2026
46bf2a0
[IMP] estate: Add best_price on Property
ollet-odoo Sep 16, 2026
2d64581
[IMP] estate: add deadline and validity on property
ollet-odoo Sep 16, 2026
5186b8a
[IMP] estate: set fields default for garden
ollet-odoo Sep 16, 2026
faa8936
[IMP] estate: add cancel and sold button on property
ollet-odoo Sep 16, 2026
3e4fb86
[IMP] estate: ability to refuse or refuse offers
ollet-odoo Sep 16, 2026
5d2b876
[IMP] estate: add data validation constraints
ollet-odoo Sep 16, 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 @@
# noinspection statement-effect
{
'name': 'estate',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_menus.xml'
],
'author': "Odoo S.A.",
'license': 'AGPL-3'
}
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 .property import Property
from .property_type import PropertyType
from .property_tag import PropertyTag
from .property_offer import PropertyOffer
106 changes: 106 additions & 0 deletions estate/models/property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from odoo import models, fields, api, _
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 = "Real estate property"

name = fields.Char(string="Title", required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(copy=False, default=lambda x: fields.Date.add(fields.Date.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)")
Comment thread
ollet-odoo marked this conversation as resolved.
faces = fields.Integer(string="Facades")
has_garage = fields.Boolean(string="Garage")
has_garden = fields.Boolean(string="Garden")
garden_area = fields.Integer(string="Garden Area (sqm)")
garden_orientation = fields.Selection(
selection=[
('north', "North"),
('east', "East"),
('south', "South"),
('west', "West")
]
)
active = fields.Boolean(default=True)
state = fields.Selection(
default='new',
required=True,
copy=False,
selection=[
('new', "New"),
('offer_received', "Offer Received"),
('offer_accepted', "Offer Accepted"),
('sold', "Sold"),
('cancelled', "Cancelled")
]
)
property_type_id = fields.Many2one("estate.property.type", string="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.tag")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")

total_area = fields.Float(compute='_compute_total_area')

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

best_price = fields.Float(string="Best Offer", compute="_compute_best_price")

@api.depends("offer_ids.price")
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 = None

@api.onchange('has_garden')
def _onchange_has_garden(self):
if self.has_garden:
self.garden_area = 10
self.garden_orientation = 'north'
else:
self.garden_area = 0
self.garden_orientation = ''

def action_do_sold(self):
for record in self:
if record.state == 'cancelled':
raise UserError('Canceled property cannot be sold')
else:
record.state = 'sold'
return True

def action_do_cancel(self):
for record in self:
if record.state == 'sold':
raise UserError('Sold property cannot be canceled')
else:
record.state = 'cancelled'

# Constraints
_check_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'Expected price must be strictly positive',
)
_check_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'Expected price must be positive',
)

@api.constrains('expected_price', 'selling_price')
def _check_expected_price_selling_price(self):
for record in self:
if not float_is_zero(self.selling_price, precision_digits=2):
if float_compare(self.expected_price * 0.90, self.selling_price, precision_digits=2) == 1:
raise ValidationError("Selling price must be above 90% of expected price")
52 changes: 52 additions & 0 deletions estate/models/property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from odoo import models, fields, api, exceptions


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

price = fields.Float()
status = fields.Selection(
copy=False,
selection=[
('accepted', "Accepted"),
('refused', "Refused"),
]
)
partner_id = fields.Many2one('res.partner', string="Partner", 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")

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


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


def action_accept(self):
for record in self:
if record.property_id.state in ('offer_accepted', 'sold', 'cancelled'):
raise exceptions.UserError("Cannot accept offer on property that is {}".format(record.property_id.state))
record.status = record.status = 'accepted'
record.property_id.buyer_id = self.partner_id
record.property_id.selling_price = record.price
record.property_id.state = 'offer_accepted'
return True

def action_refuse(self):
for record in self:
record.status = 'refused'
return True

_check_price = models.Constraint(
'CHECK(price > 0)',
'Offer price must be strictly positive',
)
12 changes: 12 additions & 0 deletions estate/models/property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models, fields


class PropertyTag(models.Model):
_name = 'estate.property.tag'
_description = "Property Tag"

name = fields.Char(string="Name", required=True)
_name_uniq = models.Constraint(
'UNIQUE (name)',
"The name of the tag must be unique!",
)
12 changes: 12 additions & 0 deletions estate/models/property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models, fields


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

name = fields.Char(string="Name", required=True)
_name_uniq = models.Constraint(
'UNIQUE (name)',
"The name of the type must 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
12 changes: 12 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_advertisements_menu" name="Advertisements">
<menuitem id="estate_property_menu_action" action="estate_property_action"/>
</menuitem>
<menuitem id="estate_settgins_menu" name="Settings">
<menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</odoo>
35 changes: 35 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_offer_view_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>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="action_accept" title="Accept" type="object" icon="fa-check"/>
<button name="action_refuse" title="Refuse" type="object"
icon="oi-close"/>

<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="Property">
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="date_deadline"/>
<field name="validity"/>
<field name="status"/>
</group>
</form>
</field>
</record>
</odoo>
43 changes: 43 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list,form</field>
</record>
<record id="estate_property_tag_view_tree" model="ir.ui.view">
<field name="name">estate.property.tag.list</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<list string="Properties">
<field name="name"/>
</list>
</field>
</record>
<record id="estate_property_tag_view_form" model="ir.ui.view">
<field name="name">estate.property.tag.form</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<form string="Property">
<sheet>
<div class="oe_title">
<h1>
<group>
<field name="name"/>
</group>
</h1>
</div>
</sheet>
</form>
</field>
</record>
<record id="estate_property_tag_view_search" model="ir.ui.view">
<field name="name">estate.property.tag.search</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
</search>
</field>
</record>
</odoo>
41 changes: 41 additions & 0 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<odoo>
<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_type_view_tree" model="ir.ui.view">
<field name="name">estate.property.type.list</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<list string="Properties">
<field name="name"/>
</list>
</field>
</record>
<record id="estate_property_type_view_form" model="ir.ui.view">
<field name="name">estate.property.type.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form string="Property">
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
</sheet>
</form>
</field>
</record>
<record id="estate_property_type_view_search" model="ir.ui.view">
<field name="name">estate.property.type.search</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
</search>
</field>
</record>
</odoo>
Loading