From b5920113dfa0f83fbc54632bde5c6a2ec1ebe43c Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 11:48:53 +0200 Subject: [PATCH 01/24] [ADD] estate: Add real estate module scafolding task-6573598 --- estate/__init__.py | 0 estate/__manifest__.py | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..483b1ddea71 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,7 @@ +# noinspection statement-effect +{ + 'name': 'estate', + 'depends': ['base'], + 'author': "Olivier Le Thanh Duong", + 'license': 'AGPL-3' +} From 9282f175ef5b590432218c1c83cb528041c7afed Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 14:12:07 +0200 Subject: [PATCH 02/24] [IMP] estate: Add estate.property model task-6573598 --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/property.py | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/property.py diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..9a7e03eded3 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..3ffb8215d3c --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from .property import Property diff --git a/estate/models/property.py b/estate/models/property.py new file mode 100644 index 00000000000..bf8569206ff --- /dev/null +++ b/estate/models/property.py @@ -0,0 +1,21 @@ +from odoo import models, fields + + +class Property(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() + faces = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=(('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West'))) From 339767a40e0e89065540863a952f4f8b6507d040 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 14:35:11 +0200 Subject: [PATCH 03/24] [IMP] estate: add first access rule to estate_property task-6573598 --- estate/__manifest__.py | 3 +++ estate/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 483b1ddea71..ea86803af49 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,6 +2,9 @@ { 'name': 'estate', 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + ], 'author': "Olivier Le Thanh Duong", 'license': 'AGPL-3' } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..7fe74a60dbf --- /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,0 \ No newline at end of file From 433fc25838a23b1065a24ca65effd6a4e8b4b47c Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 14:55:47 +0200 Subject: [PATCH 04/24] [IMP] estate: add Menu, form and list view task-6573598 --- estate/__manifest__.py | 2 ++ estate/data/estate_menus.xml | 8 ++++++++ estate/data/estate_property_views.xml | 8 ++++++++ 3 files changed, 18 insertions(+) create mode 100644 estate/data/estate_menus.xml create mode 100644 estate/data/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ea86803af49..9f7b81dcadf 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,6 +4,8 @@ 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', + 'data/estate_property_views.xml', + 'data/estate_menus.xml' ], 'author': "Olivier Le Thanh Duong", 'license': 'AGPL-3' diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml new file mode 100644 index 00000000000..9b158c63e02 --- /dev/null +++ b/estate/data/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/data/estate_property_views.xml b/estate/data/estate_property_views.xml new file mode 100644 index 00000000000..affda3b8abc --- /dev/null +++ b/estate/data/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate.property + list,form + + \ No newline at end of file From 7a55a9674d8819d1e257eb9ab98033e442f57633 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 15:24:52 +0200 Subject: [PATCH 05/24] [IMP] estate: Improve Property model --- estate/models/__init__.py | 2 +- estate/models/property.py | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 3ffb8215d3c..c7d2ea2e1b8 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from .property import Property +from .property import Property \ No newline at end of file diff --git a/estate/models/property.py b/estate/models/property.py index bf8569206ff..6f292b430bd 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -1,6 +1,13 @@ from odoo import models, fields +def in_3_month(_): + # Note something tell there is a more obvious way to do that... + # TODO: There is a date_utils with a relative delta inside odoo investigate it + today = fields.Date.today() + return today.replace(month=(today.month + 3 - 1) % 12 + 1) + + class Property(models.Model): _name = "estate.property" _description = "Real estate property" @@ -8,10 +15,10 @@ class Property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(copy=False, default=in_3_month) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(name=2, default=2) living_area = fields.Integer() faces = fields.Integer() garage = fields.Boolean() @@ -19,3 +26,8 @@ class Property(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( selection=(('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West'))) + active = fields.Boolean() + state = fields.Selection(default='new', required=True, + selection=(('new', 'New'), ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), + ('cancelled', 'Cancelled'))) From 703a61488658f4ff68a228d99bdadf19718b9b96 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 15:36:30 +0200 Subject: [PATCH 06/24] [CLN] estate: Menu, form and list view task-6573598 --- estate/models/property.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/estate/models/property.py b/estate/models/property.py index 6f292b430bd..acd172a9639 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -1,13 +1,6 @@ from odoo import models, fields -def in_3_month(_): - # Note something tell there is a more obvious way to do that... - # TODO: There is a date_utils with a relative delta inside odoo investigate it - today = fields.Date.today() - return today.replace(month=(today.month + 3 - 1) % 12 + 1) - - class Property(models.Model): _name = "estate.property" _description = "Real estate property" @@ -15,7 +8,7 @@ class Property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=in_3_month) + date_availability = fields.Date(copy=False, default=lambda x: 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(name=2, default=2) From 5ec1f5999d84322e0ee36dd608893347f5569349 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 15:49:36 +0200 Subject: [PATCH 07/24] [IMP] estate: Add list view --- estate/data/estate_property_views.xml | 15 +++++++++++++++ estate/models/property.py | 9 +++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/estate/data/estate_property_views.xml b/estate/data/estate_property_views.xml index affda3b8abc..8cee7f812e4 100644 --- a/estate/data/estate_property_views.xml +++ b/estate/data/estate_property_views.xml @@ -5,4 +5,19 @@ estate.property list,form + + estate.property.list + estate.property + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/models/property.py b/estate/models/property.py index acd172a9639..580a49e3f2f 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -5,14 +5,15 @@ class Property(models.Model): _name = "estate.property" _description = "Real estate property" - name = fields.Char(required=True) + name = fields.Char(required=True, string='Title') description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=lambda x: fields.Date.add(fields.Date.today(), months=3)) + 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(name=2, default=2) - living_area = fields.Integer() + bedrooms = fields.Integer(default=2) + living_area = fields.Integer(string="Living Area (sqm)") faces = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() From b365a35e9de8ac08349ce84d2350e16705e9d4dd Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 15:54:52 +0200 Subject: [PATCH 08/24] [FIX] estate Property set active by default --- estate/models/property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/property.py b/estate/models/property.py index 580a49e3f2f..947f9db0a14 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -20,7 +20,7 @@ class Property(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( selection=(('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West'))) - active = fields.Boolean() + active = fields.Boolean(default=True) state = fields.Selection(default='new', required=True, selection=(('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), From 2dd8a2039e2d7e88c7ee0bddf2a2fd693e36f3b3 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 15:58:46 +0200 Subject: [PATCH 09/24] [IMP] estate: Property do not duplicate state --- estate/models/property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/property.py b/estate/models/property.py index 947f9db0a14..7bddbe41e2f 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -21,7 +21,7 @@ class Property(models.Model): 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, + state = fields.Selection(default='new', required=True, copy=False, selection=(('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled'))) From 8badc9bd61572bc0bea797722a422064b32b167e Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 16:14:39 +0200 Subject: [PATCH 10/24] [IMP] estate: Property form --- estate/data/estate_property_views.xml | 40 ++++++++++++++++++++++++++- estate/models/property.py | 4 +-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/estate/data/estate_property_views.xml b/estate/data/estate_property_views.xml index 8cee7f812e4..05aa4880bd6 100644 --- a/estate/data/estate_property_views.xml +++ b/estate/data/estate_property_views.xml @@ -5,7 +5,7 @@ estate.property list,form - + estate.property.list estate.property @@ -20,4 +20,42 @@ + + estate.property.form + estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ \ No newline at end of file diff --git a/estate/models/property.py b/estate/models/property.py index 7bddbe41e2f..e0db5102ee9 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -14,10 +14,10 @@ class Property(models.Model): selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) living_area = fields.Integer(string="Living Area (sqm)") - faces = fields.Integer() + faces = fields.Integer(string="Facade") garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() + 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) From 18d1370823ee99c06a1e66309905ed0c5e3c2b55 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 16:52:21 +0200 Subject: [PATCH 11/24] [CLN] estate: Correct review comments --- estate/__init__.py | 2 +- estate/__manifest__.py | 2 +- estate/data/estate_menus.xml | 4 ++-- estate/data/estate_property_views.xml | 6 +++--- estate/models/__init__.py | 2 +- estate/models/property.py | 29 ++++++++++++++++++--------- estate/security/ir.model.access.csv | 2 +- 7 files changed, 29 insertions(+), 18 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/__manifest__.py b/estate/__manifest__.py index 9f7b81dcadf..6f55517b414 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,6 @@ 'data/estate_property_views.xml', 'data/estate_menus.xml' ], - 'author': "Olivier Le Thanh Duong", + 'author': "Odoo S.A.", 'license': 'AGPL-3' } diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml index 9b158c63e02..b343e7776c8 100644 --- a/estate/data/estate_menus.xml +++ b/estate/data/estate_menus.xml @@ -1,8 +1,8 @@ - + - \ No newline at end of file + diff --git a/estate/data/estate_property_views.xml b/estate/data/estate_property_views.xml index 05aa4880bd6..fdf950a6b09 100644 --- a/estate/data/estate_property_views.xml +++ b/estate/data/estate_property_views.xml @@ -45,7 +45,8 @@ - + + @@ -57,5 +58,4 @@
- - \ No newline at end of file + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c7d2ea2e1b8..3ffb8215d3c 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from .property import Property \ No newline at end of file +from .property import Property diff --git a/estate/models/property.py b/estate/models/property.py index e0db5102ee9..2b26ad37b1e 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -2,10 +2,10 @@ class Property(models.Model): - _name = "estate.property" + _name = 'estate.property' _description = "Real estate property" - name = fields.Char(required=True, string='Title') + 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), @@ -14,14 +14,25 @@ class Property(models.Model): selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) living_area = fields.Integer(string="Living Area (sqm)") - faces = fields.Integer(string="Facade") - garage = fields.Boolean() - garden = fields.Boolean() + 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'))) + 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'))) + selection=[ + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled") + ] + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 7fe74a60dbf..a2df8a5d153 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +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,0 \ No newline at end of file +estate.access_estate_property_user,access_estate_property,model_estate_property,base.group_user,1,1,1,0 \ No newline at end of file From 162db65d429a1926dd8012678c03b637ce99b775 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 17:06:40 +0200 Subject: [PATCH 12/24] [IMP] estate: display name and state in views --- estate/data/estate_property_views.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/estate/data/estate_property_views.xml b/estate/data/estate_property_views.xml index fdf950a6b09..05c90f6e8cb 100644 --- a/estate/data/estate_property_views.xml +++ b/estate/data/estate_property_views.xml @@ -11,6 +11,7 @@ + @@ -26,6 +27,14 @@
+
+

+ +

+
+ + + From 0afb8b89df0cd5a73d5c1112414872391f898de3 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Tue, 15 Sep 2026 17:10:30 +0200 Subject: [PATCH 13/24] [IMP] estate: add search, filter and group by on property --- estate/data/estate_property_views.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/estate/data/estate_property_views.xml b/estate/data/estate_property_views.xml index 05c90f6e8cb..8c5370891aa 100644 --- a/estate/data/estate_property_views.xml +++ b/estate/data/estate_property_views.xml @@ -67,4 +67,24 @@ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + From 7d4e7cee8eb767cb5a1aad583f1b0e98e00582a4 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 09:44:44 +0200 Subject: [PATCH 14/24] [IMP] estate: add Type for properties Add model, views, security and many2one --- estate/__manifest__.py | 5 ++- estate/data/estate_menus.xml | 8 ---- estate/models/__init__.py | 1 + estate/models/property.py | 22 ++++++---- estate/models/property_type.py | 8 ++++ estate/security/ir.model.access.csv | 3 +- estate/views/estate_menus.xml | 11 +++++ estate/views/estate_property_type_views.xml | 41 +++++++++++++++++++ .../{data => views}/estate_property_views.xml | 4 ++ 9 files changed, 83 insertions(+), 20 deletions(-) delete mode 100644 estate/data/estate_menus.xml create mode 100644 estate/models/property_type.py create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_type_views.xml rename estate/{data => views}/estate_property_views.xml (93%) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 6f55517b414..12091aa36e1 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,8 +4,9 @@ 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', - 'data/estate_property_views.xml', - 'data/estate_menus.xml' + 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_menus.xml' ], 'author': "Odoo S.A.", 'license': 'AGPL-3' diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml deleted file mode 100644 index b343e7776c8..00000000000 --- a/estate/data/estate_menus.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 3ffb8215d3c..70ac9189ba0 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,2 @@ from .property import Property +from .property_type import PropertyType diff --git a/estate/models/property.py b/estate/models/property.py index 2b26ad37b1e..2564f2680d0 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -27,12 +27,16 @@ class Property(models.Model): ] ) 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") - ] - ) + 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") diff --git a/estate/models/property_type.py b/estate/models/property_type.py new file mode 100644 index 00000000000..27bb3f275af --- /dev/null +++ b/estate/models/property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class PropertyType(models.Model): + _name = 'estate.property.type' + _description = "Property Type" + + name = fields.Char(string="Name", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index a2df8a5d153..16f8dc323d6 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -estate.access_estate_property_user,access_estate_property,model_estate_property,base.group_user,1,1,1,0 \ No newline at end of file +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 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..4845e9f3f7f --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..58b70005dbf --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,41 @@ + + + + Property Types + estate.property.type + list,form + + + estate.property.type.list + estate.property.type + + + + + + + + estate.property.type.form + estate.property.type + + + +
+

+ +

+
+
+ +
+
+ + estate.property.type.search + estate.property.type + + + + + + +
diff --git a/estate/data/estate_property_views.xml b/estate/views/estate_property_views.xml similarity index 93% rename from estate/data/estate_property_views.xml rename to estate/views/estate_property_views.xml index 8c5370891aa..1e105396886 100644 --- a/estate/data/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -11,6 +11,7 @@ + @@ -34,6 +35,7 @@ + @@ -73,6 +75,7 @@ + @@ -83,6 +86,7 @@ + From a8353101c02f84cd70bbffd772965677c41746ae Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 10:08:52 +0200 Subject: [PATCH 15/24] [IMP] estate: add buyer and salesperson on Property --- estate/models/property.py | 2 ++ estate/views/estate_property_views.xml | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/estate/models/property.py b/estate/models/property.py index 2564f2680d0..0ee241345a4 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -40,3 +40,5 @@ class Property(models.Model): ] ) 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) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1e105396886..9fbbd7b6503 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -63,6 +63,14 @@ + + + + + + + +
From 4f8b1c9520449e99d4b47884566856e79ae1f203 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 10:25:12 +0200 Subject: [PATCH 16/24] [IMP] estate: Add Property tags add model, views, security and links --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/property.py | 1 + estate/models/property_tag.py | 8 ++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_menus.xml | 1 + estate/views/estate_property_tag_views.xml | 43 ++++++++++++++++++++++ estate/views/estate_property_views.xml | 1 + 8 files changed, 57 insertions(+) create mode 100644 estate/models/property_tag.py create mode 100644 estate/views/estate_property_tag_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 12091aa36e1..ce73d45d2d5 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,7 @@ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', 'views/estate_menus.xml' ], 'author': "Odoo S.A.", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 70ac9189ba0..45e6be88d2d 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,2 +1,3 @@ from .property import Property from .property_type import PropertyType +from .property_tag import PropertyTag diff --git a/estate/models/property.py b/estate/models/property.py index 0ee241345a4..f59e28df57f 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -42,3 +42,4 @@ class Property(models.Model): 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") diff --git a/estate/models/property_tag.py b/estate/models/property_tag.py new file mode 100644 index 00000000000..1fdc0400be0 --- /dev/null +++ b/estate/models/property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class PropertyTag(models.Model): + _name = 'estate.property.tag' + _description = "Property Tag" + + name = fields.Char(string="Name", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 16f8dc323d6..4930e1c2af2 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,3 +1,4 @@ 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 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 4845e9f3f7f..d2a755252a6 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -6,6 +6,7 @@ + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..c1a33dce056 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,43 @@ + + + + Property Tags + estate.property.tag + list,form + + + estate.property.tag.list + estate.property.tag + + + + + + + + estate.property.tag.form + estate.property.tag + +
+ +
+

+ + + +

+
+
+
+
+
+ + estate.property.tag.search + estate.property.tag + + + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9fbbd7b6503..de42bfbbfdb 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -32,6 +32,7 @@

+ From ea8305e60b267b0a860307d1e9193fae93957336 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 10:40:08 +0200 Subject: [PATCH 17/24] [IMP] estate: Add Property Offers add model, views, security and links --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/property.py | 1 + estate/models/property_offer.py | 17 +++++++++++++++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_property_offer_views.xml | 14 ++++++++++++++ estate/views/estate_property_views.xml | 6 ++++-- 7 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 estate/models/property_offer.py create mode 100644 estate/views/estate_property_offer_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ce73d45d2d5..90ff2bf1cd4 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,7 @@ '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.", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 45e6be88d2d..9baa1323f26 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,3 +1,4 @@ from .property import Property from .property_type import PropertyType from .property_tag import PropertyTag +from .property_offer import PropertyOffer diff --git a/estate/models/property.py b/estate/models/property.py index f59e28df57f..832bb958480 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -43,3 +43,4 @@ class Property(models.Model): 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") diff --git a/estate/models/property_offer.py b/estate/models/property_offer.py new file mode 100644 index 00000000000..e86e8f22169 --- /dev/null +++ b/estate/models/property_offer.py @@ -0,0 +1,17 @@ +from odoo import models, fields + + +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) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4930e1c2af2..be13be70d5f 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,3 +2,4 @@ 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 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..1e0679d1c63 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,14 @@ + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index de42bfbbfdb..f2b8c336c46 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -46,7 +46,6 @@ - @@ -64,6 +63,9 @@ + + + @@ -72,10 +74,10 @@ + - From c3845d7679f5911d46d62c65e403a2551eec6f46 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 11:09:12 +0200 Subject: [PATCH 18/24] [IMP] estate: Add living_area on Property --- estate/models/property.py | 9 ++++++++- estate/views/estate_property_views.xml | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/estate/models/property.py b/estate/models/property.py index 832bb958480..e1f51f0afff 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import models, fields, api class Property(models.Model): @@ -44,3 +44,10 @@ class Property(models.Model): 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 \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f2b8c336c46..99525b73e05 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -60,6 +60,7 @@ + From e7eabbf7506f667e7bbf3dcf3745fab9734ba381 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 11:14:10 +0200 Subject: [PATCH 19/24] [IMP] estate: Add best_price on Property --- estate/models/property.py | 10 +++++++++- estate/views/estate_property_views.xml | 3 ++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/estate/models/property.py b/estate/models/property.py index e1f51f0afff..c49074d0f5f 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -50,4 +50,12 @@ class Property(models.Model): @api.depends('garden_area', 'living_area') def _compute_total_area(self): for record in self: - record.total_area = record.garden_area + record.living_area \ No newline at end of file + 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: + # TODO: We should filter out rejected offers + record.best_price = max(record.offer_ids.mapped("price")) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 99525b73e05..37e6b71a9da 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -44,8 +44,9 @@ - + + From dd0f6139a5210e878a2d0713da0e4da31d4bb5fc Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 11:39:16 +0200 Subject: [PATCH 20/24] [IMP] estate: add deadline and validity on property --- estate/models/property_offer.py | 15 ++++++++++++++- estate/views/estate_property_offer_views.xml | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/estate/models/property_offer.py b/estate/models/property_offer.py index e86e8f22169..831b1f9b8a1 100644 --- a/estate/models/property_offer.py +++ b/estate/models/property_offer.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import models, fields, api class PropertyOffer(models.Model): @@ -15,3 +15,16 @@ class PropertyOffer(models.Model): ) 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 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 1e0679d1c63..4d15e007d67 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,8 +7,25 @@ + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
From b24f6c7f7606972dbf67b65e8ca8a942fc847a29 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 11:43:17 +0200 Subject: [PATCH 21/24] [IMP] estate: set fields default for garden --- estate/models/property.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/estate/models/property.py b/estate/models/property.py index c49074d0f5f..127a1ee145b 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -59,3 +59,13 @@ def _compute_best_price(self): for record in self: # TODO: We should filter out rejected offers record.best_price = max(record.offer_ids.mapped("price")) + + + @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 = '' \ No newline at end of file From c8db864817143c378598d56ec3802de4de7e1996 Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 12:30:37 +0200 Subject: [PATCH 22/24] [IMP] estate: add cancel and sold button on property --- estate/models/property.py | 22 +++++++++++++++++++--- estate/views/estate_property_views.xml | 6 +++++- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/estate/models/property.py b/estate/models/property.py index 127a1ee145b..dd4030aa8f9 100644 --- a/estate/models/property.py +++ b/estate/models/property.py @@ -1,4 +1,5 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ +from odoo.exceptions import UserError class Property(models.Model): @@ -60,7 +61,6 @@ def _compute_best_price(self): # TODO: We should filter out rejected offers record.best_price = max(record.offer_ids.mapped("price")) - @api.onchange('has_garden') def _onchange_has_garden(self): if self.has_garden: @@ -68,4 +68,20 @@ def _onchange_has_garden(self): self.garden_orientation = 'north' else: self.garden_area = 0 - self.garden_orientation = '' \ No newline at end of file + 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' diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 37e6b71a9da..411f161b156 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -27,6 +27,10 @@ estate.property
+
+

@@ -61,7 +65,7 @@ - + From 774457f3ae4535083074fd2e6c448cd72abfe81a Mon Sep 17 00:00:00 2001 From: Olivier Le Thanh Duong Date: Wed, 16 Sep 2026 12:47:48 +0200 Subject: [PATCH 23/24] [IMP] estate: ability to refuse or refuse offers --- estate/models/property_offer.py | 18 +++++++++++++++++- estate/views/estate_property_offer_views.xml | 6 +++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/estate/models/property_offer.py b/estate/models/property_offer.py index 831b1f9b8a1..9e165b7eed5 100644 --- a/estate/models/property_offer.py +++ b/estate/models/property_offer.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, exceptions class PropertyOffer(models.Model): @@ -28,3 +28,19 @@ 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'): + err_msg = f"Cannot accept offer on property that is {record.property_id.state}" + raise exceptions.UserError(err_msg) + 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 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 4d15e007d67..d51b13c73e4 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,8 +7,12 @@ - + +