From 9efdf599a3905424a92ec324d48388d06dee5838 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Mon, 19 Jan 2026 16:10:52 +0100 Subject: [PATCH 01/15] First try --- awesome_clicker/__manifest__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py index 56dc2f779b9..c436012c188 100644 --- a/awesome_clicker/__manifest__.py +++ b/awesome_clicker/__manifest__.py @@ -25,5 +25,7 @@ ], }, - 'license': 'AGPL-3' + 'license': 'AGPL-3' + + } From b75db3bf87c265cc6e01a81d6a7fff822f8c2621 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Tue, 20 Jan 2026 09:31:23 +0100 Subject: [PATCH 02/15] [ADD] some elements added --- estate/__init__.py | 1 + estate/__manifest__.py | 14 ++++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 27 +++++++++++++++++++++++++++ 4 files changed, 43 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..e625d42dc59 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "Estate", + "version": "1.0", + "summary": "Real Estate Property Management", + "description": """ + Tutorial module for managing real estate properties. + """, + "author": "Your Name", + "category": "Tutorial", + "depends": ["base"], + "data": [], + "installable": True, + "application": True, +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..68236253574 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import fields, models + + +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() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] + ) From a29cdcec5779ae0172bba9972bf0e52f0dd60ea5 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Tue, 20 Jan 2026 09:59:04 +0100 Subject: [PATCH 03/15] [ADD] elements added 2 --- estate/__manifest__.py | 4 +++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e625d42dc59..a7bbbee6483 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,7 +8,9 @@ "author": "Your Name", "category": "Tutorial", "depends": ["base"], - "data": [], + "data": [ + 'security/ir.model.access.csv' + ], "installable": True, "application": True, } \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..98f4671fb0d --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 From c3295cb218a53acc16d70a08c43bd3bd51d9e53a Mon Sep 17 00:00:00 2001 From: eesteerina <126086200+eesteerina@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:27:27 +0100 Subject: [PATCH 04/15] Update awesome_clicker/__manifest__.py Co-authored-by: Arthur Nanson --- awesome_clicker/__manifest__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py index c436012c188..4295043399b 100644 --- a/awesome_clicker/__manifest__.py +++ b/awesome_clicker/__manifest__.py @@ -26,6 +26,4 @@ }, 'license': 'AGPL-3' - - } From 0ef923a578f4eab38a5dd803d02502ce531e40a6 Mon Sep 17 00:00:00 2001 From: eesteerina <126086200+eesteerina@users.noreply.github.com> Date: Tue, 20 Jan 2026 11:27:49 +0100 Subject: [PATCH 05/15] Update estate/models/estate_property.py Co-authored-by: Arthur Nanson --- estate/models/estate_property.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 68236253574..daa40ec4041 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -19,9 +19,9 @@ class EstateProperty(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( selection=[ - ("north", "North"), - ("south", "South"), - ("east", "East"), - ("west", "West"), + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), ] ) From 34a537ff5a500023d4880f50b77729bd8d104174 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Tue, 20 Jan 2026 11:35:05 +0100 Subject: [PATCH 06/15] [ADD] estate: add menus and action for property model This addition will allow to make the newly created model usable through the Odoo frontend and to validate access rights and views integration. --- awesome_clicker/__manifest__.py | 3 ++- estate/__manifest__.py | 8 +++++--- estate/models/__init__.py | 2 +- estate/views/estate_menus.xml | 15 +++++++++++++++ estate/views/estate_property_views.xml | 8 ++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py index 4295043399b..7e15d9dd616 100644 --- a/awesome_clicker/__manifest__.py +++ b/awesome_clicker/__manifest__.py @@ -25,5 +25,6 @@ ], }, - 'license': 'AGPL-3' + 'license': 'AGPL-3' } + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a7bbbee6483..23bf37dbd3c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,5 @@ { - "name": "Estate", + "name": "Real Estate", "version": "1.0", "summary": "Real Estate Property Management", "description": """ @@ -9,8 +9,10 @@ "category": "Tutorial", "depends": ["base"], "data": [ - 'security/ir.model.access.csv' + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' ], "installable": True, "application": True, -} \ No newline at end of file +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property \ No newline at end of file +from . import estate_property diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..58c0f44d08f --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..085b6da603e --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate.property + list,form + + From e7ec16b0ee5e2fcab5555abd08b784c082814001 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Tue, 20 Jan 2026 13:40:50 +0100 Subject: [PATCH 07/15] [IMP] estate: add defaults and reserved fields to property model This commit enhances the estate.property model by introducing reserved fields and field attributes required in the UI. --- awesome_clicker/__manifest__.py | 4 +-- estate/models/estate_property.py | 42 +++++++++++++++++++++----- estate/views/estate_menus.xml | 16 +++++----- estate/views/estate_property_views.xml | 10 +++--- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py index 7e15d9dd616..3247ccdcc1a 100644 --- a/awesome_clicker/__manifest__.py +++ b/awesome_clicker/__manifest__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Clicker", + 'name': 'Awesome Clicker', 'summary': """ Starting module for "Master the Odoo web framework, chapter 1: Build a Clicker game" @@ -10,7 +10,7 @@ Starting module for "Master the Odoo web framework, chapter 1: Build a Clicker game" """, - 'author': "Odoo", + 'author': 'Odoo', 'website': "https://www.odoo.com/", 'category': 'Tutorials', 'version': '0.1', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index daa40ec4041..1af948c7127 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,17 +1,30 @@ from odoo import fields, models +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): - _name = "estate.property" - _description = "Real Estate Property" + _name = 'estate.property' + _description = 'Real Estate Property' - name = fields.Char(required=True) + name = fields.Char( + required=True + ) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() - expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + date_availability = fields.Date( + copy=False, + default=lambda self: fields.Date.context_today(self) + relativedelta(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() garage = fields.Boolean() @@ -25,3 +38,18 @@ class EstateProperty(models.Model): ('west', 'West'), ] ) + state = fields.Selection( + [ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Cancelled'), + ], + required=True, + copy=False, + default='new', + ) + active = fields.Boolean( + default=True + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 58c0f44d08f..af396848582 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,15 +1,15 @@ - + - + - + - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 085b6da603e..3f9f9e3e082 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,8 +1,8 @@ - + - - Properties - estate.property - list,form + + Properties + estate.property + list,form From ebfc72cf96a2d9c2216b2d5b6c2173ee2ab4e98c Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Tue, 20 Jan 2026 16:08:04 +0100 Subject: [PATCH 08/15] [IMP] estate: improve property views usability The default auto-generated views are not suitable for real usage. This change provides a clearer and more efficient UI for users by presenting the key information in a structured way and enabling faster retrieval of relevant properties via dedicated search options. --- estate/__manifest__.py | 1 + estate/views/estate_property_views.xml | 85 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 23bf37dbd3c..605dcc1136a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -15,4 +15,5 @@ ], "installable": True, "application": True, + 'license': 'LGPL-3', } diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3f9f9e3e082..5449e21f84e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,4 +5,89 @@ estate.property list,form + + + estate.property.view.list + estate.property + + + + + + + + + + + + + + + + estate.property.view.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.view.search + estate.property + + + + + + + + + + + + + + + + + From f3d41a72bc3da7e7dc9c071470f93ad56948e7e1 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Wed, 21 Jan 2026 11:38:55 +0100 Subject: [PATCH 09/15] [ADD] add property types, property tags and offers This addition will allow to have categories of types and tags for a property and track all the offers made for a specific property --- estate/__manifest__.py | 3 + estate/models/__init__.py | 3 + estate/models/estate_property.py | 26 +++-- estate/models/estate_property_offer.py | 17 +++ estate/models/estate_property_tag.py | 8 ++ estate/models/estate_property_type.py | 8 ++ estate/security/ir.model.access.csv | 3 + estate/views/estate_menus.xml | 16 ++- estate/views/estate_property_offer_views.xml | 27 +++++ estate/views/estate_property_tag_views.xml | 18 +++ estate/views/estate_property_type_views.xml | 19 ++++ estate/views/estate_property_views.xml | 111 +++++++++++-------- 12 files changed, 199 insertions(+), 60 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 605dcc1136a..43b00f6a841 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,9 @@ "depends": ["base"], "data": [ 'security/ir.model.access.csv', + 'views/estate_property_offer_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_type_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml' ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1af948c7127..93a102422ab 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -6,25 +6,19 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate Property' - name = fields.Char( - required=True - ) + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date( copy=False, default=lambda self: fields.Date.context_today(self) + relativedelta(months=3), ) - expected_price = fields.Float( - required=True, - ) + expected_price = fields.Float(required=True) selling_price = fields.Float( readonly=True, copy=False ) - bedrooms = fields.Integer( - default=2, - ) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -50,6 +44,14 @@ class EstateProperty(models.Model): copy=False, default='new', ) - active = fields.Boolean( - default=True - ) + active = fields.Boolean(default=True) + + 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='Salesperson', default=lambda self: self.env.user) + + tag_ids = fields.Many2many('estate.property.tag', string='Property Tag') + + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..85f37592db6 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Estate Property Offer' + + partner_id = fields.Many2one('res.partner', string='Partner', required=True) + property_id = fields.Many2one('estate.property', string='Estate Property', required=True) + price = fields.Float() + status = fields.Selection( + copy=False, + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ] + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..fef8d256bae --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Estate Property Tag' + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..8e04ff22035 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Estate Property Type' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 98f4671fb0d..0c0b62b7fee 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 +estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 +estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index af396848582..4b9ce3396f3 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,12 +4,24 @@ - + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..6230583bdc8 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,27 @@ + + + + estate.property.offer.view.list + estate.property.offer + + + + + + + + + + + estate.property.offer.view.form + estate.property.offer + +
+ + + + + +
+ +
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..0dae4b4d00c --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,18 @@ + + + + Property Tag + estate.property.tag + list,form + + + + estate.property.tag.view.list + estate.property.tag + + + + + + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..460d054fe4c --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,19 @@ + + + + Property Type + estate.property.type + list,form + + + + estate.property.type.view.list + estate.property.type + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5449e21f84e..9935db6dfc3 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,61 +6,78 @@ list,form - - estate.property.view.list - estate.property - - - - - - - - - - + + estate.property.view.list + estate.property + + + + + + + + + + - - estate.property.view.form - estate.property - -
+ + estate.property.view.form + estate.property + + -
+

- +

+
+ +
+ - - + + - - + + - + - + - - - - - - - + + + + + + + + + + + + + + + + + + + + @@ -70,24 +87,26 @@ - - estate.property.view.search - estate.property - - - - - - - - + + estate.property.view.search + estate.property + + + + + + + + - + - + + + From ed9fb0b2f209bad0394cebc38da27e98d849ab02 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Wed, 21 Jan 2026 11:49:11 +0100 Subject: [PATCH 10/15] [IMP] corrections --- estate/__init__.py | 2 +- estate/models/estate_property.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 93a102422ab..98f2e3c51a4 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,15 +9,9 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date( - copy=False, - default=lambda self: fields.Date.context_today(self) + relativedelta(months=3), - ) + date_availability = fields.Date(copy=False, default=lambda self: fields.Date.context_today(self) + relativedelta(months=3)) expected_price = fields.Float(required=True) - selling_price = fields.Float( - readonly=True, - copy=False - ) + selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() From a4130adcf80af4f2c777a3909c6a131540db7f13 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Wed, 21 Jan 2026 16:18:03 +0100 Subject: [PATCH 11/15] [IMP] Improved property with best price calculation, validity, deadline Onchange on validity and deadline will allow consistency between the two and the calculation of best price will simplify the procedure to user. --- estate/models/estate_property.py | 40 +++++++++++++++++++- estate/models/estate_property_offer.py | 20 +++++++++- estate/views/estate_property_offer_views.xml | 14 +++++-- estate/views/estate_property_views.xml | 8 ++++ 4 files changed, 76 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 98f2e3c51a4..f415d89a3f6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models from dateutil.relativedelta import relativedelta @@ -49,3 +49,41 @@ class EstateProperty(models.Model): tag_ids = fields.Many2many('estate.property.tag', string='Property Tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + + total_area = fields.Integer(compute='_compute_total_area') + + best_price = fields.Float(compute='_compute_best_price') + + @api.depends('garden_area', 'total_area') + def _compute_total_area(self): + for record in self: + record.total_area = record.garden_area + record.living_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped('price') + record.best_price = max(prices, default=0.0) + + @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 == 'canceled': + raise UserError('A canceled property cannot be set as sold.') + record.state = 'sold' + return True + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError('A sold property cannot be canceled.') + record.state = 'canceled' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 85f37592db6..88ed974ef2b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,5 @@ -from odoo import fields, models - +from odoo import api, fields, models +from datetime import timedelta class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' @@ -15,3 +15,19 @@ class EstatePropertyOffer(models.Model): ('refused', 'Refused'), ] ) + + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline', store=True) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for offer in self: + create_dt = offer.create_date if offer.create_date else fields.Datetime.now() + offer.date_deadline = (create_dt + timedelta(days=offer.validity)).date() + + + def _inverse_date_deadline(self): + for offer in self: + if offer.date_deadline: + create_date = offer.create_date.date() if offer.create_date else fields.Datetime.now() + offer.validity = (offer.date_deadline - create_date).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 6230583bdc8..0c70b6352e2 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,8 @@ + + @@ -17,9 +19,15 @@ estate.property.offer - - - + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9935db6dfc3..fa5fb20892a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -19,6 +19,7 @@ + @@ -28,6 +29,11 @@ estate.property
+
+

@@ -46,6 +52,7 @@ + @@ -65,6 +72,7 @@ + From 60d1ea29b2c39c0857381b168b59ee6266eeebee Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Wed, 21 Jan 2026 17:23:55 +0100 Subject: [PATCH 12/15] [ADD] confirmation buttons and interactions added Now it is possible to have a flow for the events makeing an interaction between the property and the offer. --- estate/models/estate_property.py | 24 ++++++++++++- estate/models/estate_property_offer.py | 36 ++++++++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 7 ++-- 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f415d89a3f6..26141cf0e57 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from odoo import api, fields, models from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -85,5 +86,26 @@ def action_cancel(self): for record in self: if record.state == 'sold': raise UserError('A sold property cannot be canceled.') - record.state = 'canceled' + record.state = 'new' + return True + + def action_set_new(self): + for record in self: + if record.state in ('sold', 'canceled'): + raise UserError('You cannot move a sold/canceled property back to New.') + record.state = 'new' + return True + + def action_set_offer_received(self): + for record in self: + if record.state in ('sold', 'canceled'): + raise UserError('You cannot change the state of a sold/canceled property.') + record.state = 'offer_received' + return True + + def action_set_offer_accepted(self): + for record in self: + if record.state in ('sold', 'canceled'): + raise UserError('You cannot change the state of a sold/canceled property.') + record.state = 'offer_accepted' return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 88ed974ef2b..9fbeb853c2f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from odoo import api, fields, models from datetime import timedelta +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' @@ -31,3 +32,38 @@ def _inverse_date_deadline(self): if offer.date_deadline: create_date = offer.create_date.date() if offer.create_date else fields.Datetime.now() offer.validity = (offer.date_deadline - create_date).days + + def action_confirm(self): + for offer in self: + if offer.property_id.state in ('offer_accepted', 'sold', 'canceled'): + raise UserError('A sold property cannot accept new offers.') + + offer.status = 'accepted' + + offer.property_id.write({ + 'buyer_id': offer.partner_id.id, + 'selling_price': offer.price, + 'state': 'offer_accepted', + }) + + return True + + def action_refuse(self): + for offer in self: + if offer.status == 'accepted': + raise UserError('You cannot refuse an accepted offer.') + + offer.status = 'refused' + + property_record = offer.property_id + + active_offers = property_record.offer_ids.filtered(lambda o: o.status in ('pending', 'accepted')) + + if not active_offers: + property_record.write({ + 'buyer_id': False, + 'selling_price': 0, + 'state': 'new', + }) + + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 0c70b6352e2..e538c6d92ef 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,6 +9,8 @@ + +

+
+

+ +

+
+ + + + + + + + + + + + +
+
+
+ diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4ca2ffc81a4..d280df75a31 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,13 +4,14 @@ Properties estate.property list,form + {'search_default_available': 1} estate.property.view.list estate.property - + @@ -18,7 +19,7 @@ - + @@ -30,12 +31,9 @@
-
@@ -45,11 +43,12 @@
- +
+ @@ -73,15 +72,15 @@ - - + + - + @@ -107,7 +106,7 @@ - + From 9386f037359b96d0536bac2b8e5e3d8a6fc9d830 Mon Sep 17 00:00:00 2001 From: Ester Andreetto Date: Thu, 22 Jan 2026 17:14:50 +0100 Subject: [PATCH 15/15] [IMP] inheritance in some models Created some methods for model inheritance to improve for example the res.users model. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 12 ++++++++---- estate/models/estate_property_offer.py | 8 ++++++++ estate/models/estate_property_type.py | 2 +- estate/models/res_users.py | 6 ++++++ estate/views/estate_property_views.xml | 1 + estate/views/res_users_views.xml | 24 ++++++++++++++++++++++++ 8 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 43b00f6a841..98e1055a2a5 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ "depends": ["base"], "data": [ 'security/ir.model.access.csv', + 'views/res_users_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_property_type_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8d60307a3e6..ee284d9cfb5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,6 @@ from odoo import api, fields, models from dateutil.relativedelta import relativedelta -from odoo.tools.float_utils import float_compare, float_is_zero +from odoo.tools.float_utils import float_is_zero from odoo.exceptions import UserError, ValidationError @@ -72,9 +72,7 @@ def _check_selling_price(self): for record in self: if float_is_zero(record.selling_price, precision_digits=2): continue - - min_price = record.expected_price * 0.9 - if float_compare(record.selling_price, min_price, precision_digits=2) < 0: + if record.selling_price < record.expected_price * 0.9: raise ValidationError('The selling price cannot be lower than 90% of the expected price.') @api.depends('garden_area', 'total_area') @@ -96,6 +94,12 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_cancelled(self): + for record in self: + if record.state not in ('new', 'cancelled'): + raise UserError('You can only delete a property when its state is New or Cancelled.') def action_sold(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5632e3bf042..3a59a8ee292 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -73,3 +73,11 @@ def action_refuse(self): }) return True + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals['property_id'] and vals['price'] is not None: + if vals['price'] < self.env['estate.property'].browse(vals['property_id']).best_price: + raise UserError('The offer must be higher than the existing offers.') + return super().create(vals_list) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 0aec82d4d36..5a080c26857 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -14,7 +14,7 @@ class EstatePropertyType(models.Model): 'The property type name must be unique.', ) - property_ids = fields.One2many('estate.property','property_type_id', string='Properties') + property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties') offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Offers') offer_count = fields.Integer(compute='_compute_offer_count', string='Offer Count') diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..8acea9ae096 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,6 @@ +from odoo import fields, models + +class ResUsers(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many('estate.property', 'salesperson_id', string='Properties', domain=[('state', 'in', ['new', 'offer_received'])]) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index d280df75a31..31dc3252ef8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -15,6 +15,7 @@ + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..83417593df4 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,24 @@ + + + + + res.users.view.form.inherit.estate.property + res.users + + + + + + + + + + + + +