Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
3c4ec5c
[REF] Internal: test setup dalio
dalio-odoo Apr 21, 2026
46f010c
[ADD] estate: add model
dalio-odoo Apr 21, 2026
2a943c1
[ADD] estate: add estate_property
dalio-odoo Apr 22, 2026
25b1b95
[FIX] estate: fix style
dalio-odoo Apr 22, 2026
d9454fb
[ADD] estate: add access right for estate_property
dalio-odoo Apr 22, 2026
c767015
[FIX] estate: add author and license to manifest
dalio-odoo Apr 22, 2026
4faa6cd
[ADD] estate: add menus, actions and property fields logic
dalio-odoo Apr 22, 2026
c626a8c
[IMP] estate: add search features and apply style and security fixes
dalio-odoo Apr 22, 2026
f0442fb
[ADD] estate: add types and tags
dalio-odoo Apr 23, 2026
4ac7f31
[ADD] estate: add offers to properties
dalio-odoo Apr 23, 2026
3bd3578
[ADD] estate: add computed fields and onchanges
dalio-odoo Apr 23, 2026
7061f54
[ADD] estate: add 'Sold' and 'Canceled' buttons
dalio-odoo Apr 23, 2026
a3ebbc7
[ADD] estate: add business logic with buttons and constraints
dalio-odoo Apr 24, 2026
ae62f85
[IMP] estate: Change prices to monetary fields.
Mathilde411 Apr 24, 2026
5aea6e1
[IMP] estate: improve property type view and refactor code standards
dalio-odoo Apr 24, 2026
64b4e23
[IMP] estate: improve UI with editable lists and stat buttons
dalio-odoo Apr 27, 2026
6a5844e
[FIX] estate: add readonly to invisible status field in offer list
dalio-odoo Apr 27, 2026
67c3197
[FIX] estate: remove redundant invisible field
dalio-odoo Apr 27, 2026
6649480
[ADD] estate: add business logic for offers and extend user view
dalio-odoo Apr 28, 2026
9939864
[ADD] estate_account: automate invoices creation on sold properties
dalio-odoo Apr 28, 2026
dd2d610
[IMP] estate: add basic test cases
vandroogenbd Apr 28, 2026
6f8b1c7
[IMP] estate_account: improve syntax
dalio-odoo Apr 28, 2026
a161bf9
[FIX] estate: add missing garden information in test
dalio-odoo Apr 28, 2026
04e2c19
[ADD] estate: add grouped kanban view for properties
dalio-odoo Apr 29, 2026
e5ac3b6
[FIX] estate: fix business logic to ensure all tests are passed
dalio-odoo Apr 29, 2026
83a6497
[ADD] awsome_owl: add counters, counters' sum, cards and todo lists
dalio-odoo Apr 30, 2026
11820f7
[IMP] estate: apply suggestions for performance and constraints
dalio-odoo Apr 30, 2026
24f482b
[ADD] awesome_owl: add counters in cards
dalio-odoo Apr 30, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,6 @@ dmypy.json

# Pyre type checker
.pyre/

# VSCode preferences
.vscode/
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ tutorial's solutions, and one for the
[Master the Odoo web framework](https://www.odoo.com/documentation/latest/developer/tutorials/master_odoo_web_framework.html)
tutorial's solutions. For example, `17.0`, `17.0-discover-js-framework-solutions` and
`17.0-master-odoo-web-framework-solutions`.
# Test setup dalio
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
18 changes: 18 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "Real Estate",
"version": "19.0.1.0.0",
"depends": [
"base",
],
"data": [
"security/ir.model.access.csv",
"views/estate_property_type_views.xml",
"views/estate_property_tag_views.xml",
"views/estate_property_offer_views.xml",
"views/estate_property_views.xml",
"views/estate_menus.xml",
],
"application": True,
"author": "dalio",
"license": "LGPL-3"
}
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_type, estate_property_tag, estate_property_offer
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated
118 changes: 118 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_is_zero, float_compare


class EstateProperty(models.Model):

_name = "estate.property"
_description = "Real Estate Property"

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(default=lambda self: fields.Date.today() + relativedelta(months=3), copy=False)
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()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string="Orientation",
selection=[
("north", "North"),
("south", "South"),
("west", "West"),
("east", "East")
],
help="Orientation of the garden"
)
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")
],
help="State of the property",
required=True,
copy=False,
default="new"
)
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
salesman_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user)
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
total_area = fields.Integer(compute="_compute_total_area", string="Total area (sqm)")
best_price = fields.Float(compute="_compute_best_price", string="Best Offer")

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

if prices:
record.best_price = max(prices)
else:
record.best_price = 0.0
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated

@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 = False

def action_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError("Canceled properties cannot be sold.")
record.state = "sold"
return True
Comment on lines +111 to +116
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError("Canceled properties cannot be sold.")
record.state = "sold"
return True
def action_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError("Canceled properties cannot be sold.")
self.state = "sold"
return True

Only one assignation for the whole recordset


def action_cancel(self):
for record in self:
if record.state == "sold":
raise UserError("Sold properties cannot be canceled.")
record.state = "cancelled"
return True
Comment on lines +118 to +123
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above


_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
"the expected price must be strictly positive.",
)

_check_selling_price = models.Constraint(
"CHECK(selling_price >= 0)",
"the selling price must be positive.",
)

@api.constrains("selling_price", "expected_price")
def _check_selling_price(self):
for record in self:

if float_is_zero(record.selling_price, precision_digits=2):
continue

lower_bound = 0.9 * record.expected_price

if float_compare(record.selling_price, lower_bound, precision_digits=2) == -1:
raise ValidationError(
"The selling price must be at least 90% of the expected price! "
"You must reduce the expected price if you want to accept this offer."
)
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated
55 changes: 55 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from dateutil.relativedelta import relativedelta
from odoo import fields, models, api


class EstatePropertyOffer(models.Model):

_name = "estate.property.offer"
_description = "Property Offer"

price = fields.Float()
status = fields.Selection(
string="Status",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String not necessary

selection=[
("accepted", "Accepted"),
("refused", "Refused")
],
help="Status of the offer"
)
partner_id = fields.Many2one('res.partner', string="Partner", required=True)
property_id = fields.Many2one('estate.property', string="Property", required=True)
validity = fields.Integer(default=7, string="Validity (days)")
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", string="Deadline")

@api.depends("create_date", "validity")
def _compute_date_deadline(self):
for record in self:
base_date = record.create_date or fields.Date.today()
days_to_add = record.validity or 0
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated
record.date_deadline = base_date + relativedelta(days=days_to_add)

def _inverse_date_deadline(self):
for record in self:
base_date = fields.Date.to_date(record.create_date) or fields.Date.today()
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated

if record.date_deadline:
record.validity = (record.date_deadline - base_date).days
else:
record.validity = 0

def action_accept(self):
for record in self:
record.status = "accepted"
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id
return True

def action_refuse(self):
for record in self:
record.status = "refused"
Comment thread
Mathilde411 marked this conversation as resolved.
Outdated
return True

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


class EstatePropertyTag(models.Model):

_name = "estate.property.tag"
_description = "Property Tag"

name = fields.Char(required=True)

_unique_name = models.Constraint(
"UNIQUE(name)",
"the name must be unique.",
)
14 changes: 14 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from odoo import fields, models


class EstatePropertyType(models.Model):

_name = "estate.property.type"
_description = "Property Type"

name = fields.Char(required=True)

_unique_name = models.Constraint(
"UNIQUE(name)",
"the name 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
estate_property_access_user,estate.property.user,model_estate_property,base.group_user,1,1,1,1
estate_property_type_access_user,estate.property.type.user,model_estate_property_type,base.group_user,1,1,1,1
estate_property_tag_access_user,estate.property.tag.user,model_estate_property_tag,base.group_user,1,1,1,1
estate_property_offer_access_user,estate.property.offer.user,model_estate_property_offer,base.group_user,1,1,1,1
15 changes: 15 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">

<menuitem id="estate_menu_advertisement" name="Advertisements">
<menuitem id="estate_property_menu" action="estate_property_action"/>
</menuitem>

<menuitem id="estate_menu_configuration" name="Settings">
<menuitem id="estate_property_type_menu" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu" action="estate_property_tag_action"/>
</menuitem>

</menuitem>
</odoo>
33 changes: 33 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<odoo>
<record id="estate_property_offer_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="Offers">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="action_accept" string="Accept" type="object" icon="fa-check" invisible="status in ('offer_accepted', 'offer_refused')"/>
<button name="action_refuse" string="Refuse" type="object" icon="fa-times" invisible="status in ('offer_accepted', 'offer_refused')"/>
<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"/>
<field name="status"/>
</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_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>
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_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