Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d7275c8
[ADD] estate : init module
Rs-appez Sep 15, 2026
f0c6800
[IMP] estate : add base Property model
Rs-appez Sep 15, 2026
175d21c
[IMP] estate : add model field
Rs-appez Sep 15, 2026
d727849
[FIX] estate : add missing field
Rs-appez Sep 15, 2026
0d84524
[IMP] estate : add model access
Rs-appez Sep 15, 2026
c8a7bb7
[IMP] estate : add base action view
Rs-appez Sep 15, 2026
b9080c6
[IMP] estate : add menu view
Rs-appez Sep 15, 2026
7309ceb
[CLN] estate : format xml
Rs-appez Sep 15, 2026
65b9d0b
[FIX] estate : fix syntax
Rs-appez Sep 15, 2026
607123b
[IMP] estate : add param to the field
Rs-appez Sep 15, 2026
ae1b08e
[IMP] estate : add field default value
Rs-appez Sep 16, 2026
66bce2d
[IMP] estate : add active field
Rs-appez Sep 16, 2026
f58ca14
[IMP] estate : add state field
Rs-appez Sep 16, 2026
da48027
[IMP] estate : add postcode field
Rs-appez Sep 16, 2026
3171e7e
[IMP] estate : add property view list
Rs-appez Sep 16, 2026
de5bfaa
[IMP] estate : add property form view
Rs-appez Sep 16, 2026
5eb3c2f
[IMP] estate : add search and filter for property
Rs-appez Sep 16, 2026
0cb150b
[FIX] estate : use lambda in date_availability field
Rs-appez Sep 17, 2026
a386df4
[IMP] estate : add property type model
Rs-appez Sep 17, 2026
3d22465
[IMP] estate : add field property_type_id to the property model
Rs-appez Sep 17, 2026
b159321
[IMP] estate : add buyer and salesman field to property model
Rs-appez Sep 17, 2026
1d539f5
[IMP] estate : add property_tag model
Rs-appez Sep 17, 2026
18f441e
[IMP] estate : add tags to property model
Rs-appez Sep 17, 2026
58ef4a2
[CLN] estate : use dot naming for property model id
Rs-appez Sep 17, 2026
9cc0477
[CLN] estate : fix author
Rs-appez Sep 17, 2026
1d81021
[IMP] estate : add property offer model
Rs-appez Sep 17, 2026
9f01b6d
[CLN] estate : use self instead of _
Rs-appez Sep 17, 2026
090bc29
[CLN] estate : add newline to the end of the file
Rs-appez Sep 17, 2026
15ea870
[IMP] estate : add offers to the property model
Rs-appez Sep 17, 2026
979a4b4
[IMP] estate : add total area field to property model
Rs-appez Sep 17, 2026
8b27c3e
[IMP] estate : add more unit in area label field
Rs-appez Sep 17, 2026
c991d6e
[IMP] estate : add best price field to property model
Rs-appez Sep 17, 2026
17dbd97
[IMP] estate : add deadline and validity field to property offer model
Rs-appez Sep 17, 2026
f945a75
[IMP] estate : add onchange on garden field
Rs-appez Sep 17, 2026
5962249
[IMP] estate : add btn to sold and cancel property
Rs-appez Sep 17, 2026
e6f7271
[IMP] estate : add btn to accept/refuse offer
Rs-appez Sep 17, 2026
b59e012
[IMP] estate : asign price and buyer to property when offer is accepted
Rs-appez Sep 18, 2026
0e18f8b
[IMP] estate : add sql constraint on model
Rs-appez Sep 18, 2026
8ae7b23
[IMP] estate : add constraint on the selling price
Rs-appez Sep 18, 2026
d19029c
[IMP] estate : add inline property in property type view
Rs-appez Sep 18, 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": "estate",
"license": "LGPL-3",
"author": "Odoo S.A.",
"application": True,
"depends": ["base"],
"data": [
"views/estate_property_offer_views.xml",
"views/estate_property_tag_views.xml",
"views/estate_property_views.xml",
"views/estate_property_type_views.xml",
"views/estate_menus.xml",
"security/ir.model.access.csv",
],
}
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_offer
from . import estate_property_tag
from . import estate_property_type
126 changes: 126 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare, float_is_zero

THRESOLD_MIN_SELLING_POURCENT = 90


class Property(models.Model):
_name = "estate.property"
_description = "Estate Property"
_check_positive_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
"The expected price must be stricly positive",
)
_check_positive_selling_price = models.Constraint(
"CHECK(selling_price >= 0)",
"The selling price must be positive",
)

name = fields.Char(required=True)
postcode = fields.Char()
description = fields.Text()
date_availability = fields.Date(
copy=False,
default=lambda self: fields.Date.add(fields.Date.today(), months=3),
)
expected_price = fields.Float(required=True)
best_price = fields.Float(compute="_find_best_price")
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer("Living Area (sqm)")
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer("Garden area (sqm)")
garden_orientation = fields.Selection(
string="Orientation",
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
help="The garden orientation",
)
total_area = fields.Integer("Total Area (sqm)", compute="_compute_total_area")
active = fields.Boolean("Active", default=True)
state = fields.Selection(
string="State",
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
copy=False,
required=True,
default="new",
)
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
tags_ids = fields.Many2many("estate.property.tag")
buyer = fields.Many2one("res.partner", copy=False)
salesman = fields.Many2one("res.users", default=lambda self: self.env.user)
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")

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

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

# ------------------------------------------------------------
# CONSTRAINS
# ------------------------------------------------------------

@api.constrains("selling_price")
def _check_selling_price(self):
for record in self:
if not float_is_zero(record.selling_price, 2):
if (
float_compare(
record.selling_price,
record.expected_price * (THRESOLD_MIN_SELLING_POURCENT / 100),
2,
)
< 0
):
raise ValidationError(
_(
"The selling price cannot be lower than %s%% of the expected price",
)
% THRESOLD_MIN_SELLING_POURCENT,
)

# ------------------------------------------------------------
# ACTIONS
# ------------------------------------------------------------

def action_sold_property(self):
for record in self:
if record.state == "cancelled":
raise UserError(_("Cancelled property cannot be sold."))
record.state = "sold"
return True

def action_cancel_property(self):
for record in self:
record.state = "cancelled"
return True
57 changes: 57 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from odoo import api, fields, models


class PropertyType(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer"
_check_positive_price = models.Constraint(
"CHECK(price > 0)",
"The offer price must be stricly positive",
)

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

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

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

# ------------------------------------------------------------
# ACTIONS
# ------------------------------------------------------------

def action_accept_offer(self):
for record in self:
record.status = "accepted"
record.property_id.selling_price = record.price
record.property_id.buyer = record.partner_id

for offer in record.property_id.offer_ids:
if offer != record:
offer.status = "refused"

return True

def action_refuse_offer(self):
for record in self:
record.status = "refused"
return True
12 changes: 12 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class PropertyType(models.Model):
_name = "estate.property.tag"
_description = "Estate Property Tag"
_check_unique_name = models.Constraint(
"UNIQUE (name)",
"The name must be unique",
)

name = fields.Char(required=True)
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 PropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate Property Type"
_check_unique_name = models.Constraint(
"UNIQUE (name)",
"The name must be unique",
)

name = fields.Char(required=True)
property_ids = fields.One2many("estate.property", "property_type_id", "Property")
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_ir_estate_property_user,ir_estate_property_user,model_estate_property,base.group_user,1,1,1,1
access_ir_estate_property_type_user,ir_estate_property_type_user,model_estate_property_type,base.group_user,1,1,1,1
access_ir_estate_property_tag_user,ir_estate_property_tag_user,model_estate_property_tag,base.group_user,1,1,1,1
access_ir_estate_property_offer_user,ir_estate_property_offer_user,model_estate_property_offer,base.group_user,1,1,1,1
14 changes: 14 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_menu_first_level" name="Advertisements">
<menuitem id="estate_model_menu_action" action="estate_property_model_action" />
</menuitem>
<menuitem id="estate_setings_menu_first_level" name="Settings">
<menuitem id="estate_property_type_model_menu_action"
action="estate_property_type_model_action" />
<menuitem id="estate_property_tag_model_menu_action"
action="estate_property_tag_model_action" />
</menuitem>
</menuitem>
</odoo>
39 changes: 39 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>

<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="Estate Property">
<sheet>
<group>
<field name="price" />
<field name="validity" />
<field name="date_deadline" />
<field name="partner_id" />
<field name="status" />
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_offer_model_view_list" 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="Properties">
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="date_deadline" />
<button name="action_accept_offer" title="Accept" type="object"
icon="fa-check" />
<button name="action_refuse_offer" title="Refuse" type="object"
icon="fa-times" />
<field name="status" />
</list>
</field>
</record>
</odoo>
8 changes: 8 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="estate_property_tag_model_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>
</odoo>
49 changes: 49 additions & 0 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>

<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="Estate Property">
<sheet>
<group>

<div class="oe_title">
<h1>
<field name="name" />
</h1>
</div>
</group>
<notebook>
<page string="Properties">
<field name="property_ids">
<list>
<field name="name" string="Title" />
<field name="expected_price" />
<field name="state" sting="Status" />
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>

<record id="estate_property_type_model_view_list" 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_model_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>
</odoo>
Loading