From 4f2a153ae852b9ab84add9356d48dedb0bbb60e3 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 11:22:54 +0200 Subject: [PATCH 01/17] [ADD] estate: create estate application This added the folder estate with __init_.py and __manifest__.py with minimal configuration to add an estate application --- estate/__init__.py | 0 estate/__manifest__.py | 9 +++++++++ 2 files changed, 9 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..28bdfa43102 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,9 @@ +{ + 'name': 'Real Estate', + 'author': 'coleo', + 'license': 'LGPL-3', + 'depends': [ + 'base', + ], + 'application': True, +} \ No newline at end of file From d0e571a7a3126d2571fd7929f6375524aaf5f11b Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 13:19:43 +0200 Subject: [PATCH 02/17] [IMP] estate: create a table property with name and description --- .vscode/settings.json | 3 +++ estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 8 ++++++++ 4 files changed, 13 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..cc081eecddf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:venv" +} \ No newline at end of file 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..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..9c3d6a70969 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. see LICENSE file for full copyright and licensing details. + +from odoo import fields, models + +class Property(models.Model): + _name = "estate.property" + _description = "Estate property" \ No newline at end of file From 9d3cf5b94142e0e7c0fd6668853e94bca2450afd Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 13:33:33 +0200 Subject: [PATCH 03/17] [IMP] estate: create columns to the property table --- estate/models/estate_property.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9c3d6a70969..8631101b955 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,4 +5,21 @@ class Property(models.Model): _name = "estate.property" - _description = "Estate property" \ No newline at end of file + _description = "Estate property" + + name = fields.Char() + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float() + 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( + string='Type', + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] + ) \ No newline at end of file From 29a8dc504dfe96e9fa86b3a94a42cd881de4b1b6 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 13:39:31 +0200 Subject: [PATCH 04/17] [IMP] estate: set the column name and selling_price to not null --- estate/models/estate_property.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8631101b955..cf91618cc0e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -7,11 +7,11 @@ class Property(models.Model): _name = "estate.property" _description = "Estate property" - name = fields.Char() + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date() - expected_price = fields.Float() + expected_price = fields.Float(required=True) selling_price = fields.Float() bedrooms = fields.Integer() living_area = fields.Integer() From 02a48ac5f17b7d3ac6ecbad3d90c26f852aa1f19 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 14:36:25 +0200 Subject: [PATCH 05/17] [CLN] estate: add a newline at the end of files --- estate/__init__.py | 2 +- estate/__manifest__.py | 2 +- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 5 +---- 4 files changed, 4 insertions(+), 7 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 28bdfa43102..11af1a473c4 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,4 +6,4 @@ 'base', ], '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/models/estate_property.py b/estate/models/estate_property.py index cf91618cc0e..6fd663a46c9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. see LICENSE file for full copyright and licensing details. - from odoo import fields, models class Property(models.Model): @@ -22,4 +19,4 @@ class Property(models.Model): garden_orientation = fields.Selection( string='Type', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] - ) \ No newline at end of file + ) From c4b618c1bbd4d6b0904583c526a95b5d36245c10 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 15:07:42 +0200 Subject: [PATCH 06/17] [IMP] estate: create access rights for users --- estate/__manifest__.py | 3 +++ estate/models/estate_property.py | 1 + estate/security/ir.model.access.csv | 2 ++ 3 files changed, 6 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 11af1a473c4..529ea970999 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,5 +5,8 @@ 'depends': [ 'base', ], + 'data': [ + 'security/ir.model.access.csv', + ], 'application': True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 6fd663a46c9..43ffcff859f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from odoo import fields, models + class Property(models.Model): _name = "estate.property" _description = "Estate property" diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..c522edc63e3 --- /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 \ No newline at end of file From 4a114eca6d81b456eb11daae5acf9092b51c5e85 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 15:29:39 +0200 Subject: [PATCH 07/17] [IMP] estate: create a basic action view for property --- .vscode/settings.json | 3 --- estate/__manifest__.py | 1 + estate/views/estate_property_views.xml | 7 +++++++ 3 files changed, 8 insertions(+), 3 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 estate/views/estate_property_views.xml diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index cc081eecddf..00000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python-envs.defaultEnvManager": "ms-python.python:venv" -} \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 529ea970999..d2e7f1ae0f9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,7 @@ ], 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', ], 'application': True, } diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..5d7b83a0b97 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Porperty + estate.property + tree,form + + \ No newline at end of file From b7e9a519c66dbd25c75fae3e376aba3c54d67257 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 15:37:59 +0200 Subject: [PATCH 08/17] [IMP] estate: applied different suggestions to improve code --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 11 ++++++++--- estate/security/ir.model.access.csv | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d2e7f1ae0f9..9ff01cc37b9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Real Estate', - 'author': 'coleo', + 'author': 'Odoo S.A.', 'license': 'LGPL-3', 'depends': [ 'base', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 43ffcff859f..eb97825dbca 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -14,10 +14,15 @@ class Property(models.Model): bedrooms = fields.Integer() living_area = fields.Integer() facades = fields.Integer() - garage = fields.Boolean() - garden = fields.Boolean() + has_garage = fields.Boolean() + has_garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( string='Type', - selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ] ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index c522edc63e3..45198bc56b4 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,1 \ No newline at end of file +access_estate_property_user","access_estate_property","model_estate_property","base.group_user",1,1,1,1 \ No newline at end of file From 8c535314992d502692bccc580a80b3d723a354db Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 15:37:59 +0200 Subject: [PATCH 09/17] [IMP] estate: applied different suggestions to improve code --- estate/models/estate_property.py | 2 +- estate/views/estate_property_views.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index eb97825dbca..45cb0db2319 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -21,7 +21,7 @@ class Property(models.Model): string='Type', selection=[ ('north', 'North'), - ('south', 'South'), + ('south', 'South'), ('east', 'East'), ('west', 'West') ] diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5d7b83a0b97..b89a6b0f81f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,5 @@ - + Porperty estate.property tree,form From 9e1e2a707eea8448e2dabdee35d7c1c65fbc079e Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Tue, 15 Sep 2026 17:13:10 +0200 Subject: [PATCH 10/17] [IMP] estate: created a menu, fields, attributes and view Chapter 5 of tutorials --- estate/__manifest__.py | 1 + estate/models/estate_property.py | 28 ++++++++++++++++++++++---- estate/views/estate_menu.xml | 7 +++++++ estate/views/estate_property_views.xml | 4 ++-- 4 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 estate/views/estate_menu.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9ff01cc37b9..9bafc42e25f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,7 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_menu.xml', ], 'application': True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 45cb0db2319..9d5bc0cf2fa 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,17 +8,23 @@ 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=fields.Date.add(fields.Date.today(), months=3) + ) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float( + readonly=True, + copy=False + ) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() has_garage = fields.Boolean() has_garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Type', + string='Garden Orientation', selection=[ ('north', 'North'), ('south', 'South'), @@ -26,3 +32,17 @@ class Property(models.Model): ('west', 'West') ] ) + 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') + ], + default="new", + required=True, + copy=False + ) diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml new file mode 100644 index 00000000000..90452b0a53f --- /dev/null +++ b/estate/views/estate_menu.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index b89a6b0f81f..bcd23a1fa6a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,7 +1,7 @@ - Porperty + Porperties estate.property - tree,form + list,form \ No newline at end of file From a8cc8f4eb87685d04d154301cc159f0f189d23ff Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Wed, 16 Sep 2026 12:02:21 +0200 Subject: [PATCH 11/17] [IMP] estate: create custom views for the list and form of the properties --- estate/__manifest__.py | 4 +- estate/models/estate_property.py | 13 +++-- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menu.xml | 4 +- estate/views/estate_property_views.xml | 80 +++++++++++++++++++++++++- 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9bafc42e25f..c6f1feccf20 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,9 +2,7 @@ 'name': 'Real Estate', 'author': 'Odoo S.A.', 'license': 'LGPL-3', - 'depends': [ - 'base', - ], + 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9d5bc0cf2fa..27f937ab6b7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,15 +2,18 @@ class Property(models.Model): - _name = "estate.property" - _description = "Estate property" + _name = 'estate.property' + _description = 'Estate property' name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date( - copy=False, - default=fields.Date.add(fields.Date.today(), months=3) + copy=False, + default=lambda self: fields.Date.add( + fields.Date.today(), + months=3 + ) ) expected_price = fields.Float(required=True) selling_price = fields.Float( @@ -42,7 +45,7 @@ class Property(models.Model): ('sold', 'Sold'), ('cancelled', 'Cancelled') ], - default="new", + default='new', required=True, copy=False ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 45198bc56b4..c490ec3fb7a 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" -access_estate_property_user","access_estate_property","model_estate_property","base.group_user",1,1,1,1 \ No newline at end of file +access_estate_property_user","access_estate_property","model_estate_property","base.group_user",1,1,1,1 diff --git a/estate/views/estate_menu.xml b/estate/views/estate_menu.xml index 90452b0a53f..d570e4bb7ff 100644 --- a/estate/views/estate_menu.xml +++ b/estate/views/estate_menu.xml @@ -1,7 +1,7 @@ - + - \ No newline at end of file + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index bcd23a1fa6a..1d8a9b321f1 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,6 +2,82 @@ Porperties estate.property - list,form + list - \ No newline at end of file + + + estate.property.view.search + estate.property + + + + + + + + + + + + + + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + +

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ From f3b96afc0feef9acf13a7dd531f4331f07d5abf7 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Thu, 17 Sep 2026 10:40:55 +0200 Subject: [PATCH 12/17] [IMP] estate: create tag, type and offer with their menu action,list and form --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 14 ++++++++++ estate/models/estate_property_offer.py | 22 +++++++++++++++ estate/models/estate_property_tag.py | 8 ++++++ estate/models/estate_property_type.py | 8 ++++++ estate/security/ir.model.access.csv | 5 +++- estate/views/estate_menu.xml | 8 ++++-- estate/views/estate_property_offer_views.xml | 29 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 7 +++++ estate/views/estate_property_type_views.xml | 7 +++++ estate/views/estate_property_views.xml | 21 ++++++++++---- 12 files changed, 127 insertions(+), 8 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 c6f1feccf20..1917f54e6a9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', 'views/estate_menu.xml', ], 'application': True, 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 27f937ab6b7..0524f930d58 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -49,3 +49,17 @@ class Property(models.Model): required=True, copy=False ) + type_id = fields.Many2one('estate.property.type', string='Type') + partner_id = fields.Many2one( + 'res.partner', + string='Buyer', + index=True, + copy=False + ) + user_id = fields.Many2one('res.users', + string='Salesperson', + index=True, + default=lambda self: self.env.user + ) + tag_ids = fields.Many2many("estate.property.tag", string='Tags') + 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..b0385543044 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,22 @@ +from odoo import fields, models + + +class Offer(models.Model): + _name = 'estate.property.offer' + _description = 'Offer for a property of Estate' + + price = fields.Float(required=True) + status = fields.Selection( + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], + copy=False, + ) + partner_id = fields.Many2one( + 'res.partner', + string='Partner', + index=True, + required=True + ) + property_id = fields.Many2one('estate.property', required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..2aea838aff3 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class Type(models.Model): + _name = 'estate.property.tag' + _description = 'Tag of property of Estate' + + 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..232058ec35c --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class Type(models.Model): + _name = 'estate.property.type' + _description = 'Type of property of Estate' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index c490ec3fb7a..efdb9319ebc 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" -access_estate_property_user","access_estate_property","model_estate_property","base.group_user",1,1,1,1 +"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_menu.xml b/estate/views/estate_menu.xml index d570e4bb7ff..972ad8ba3a3 100644 --- a/estate/views/estate_menu.xml +++ b/estate/views/estate_menu.xml @@ -1,7 +1,11 @@ - - + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..3c3b05afa6a --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,29 @@ + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.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..e453ff28ea4 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,7 @@ + + + Tags + estate.property.tag + list + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..6aa0f3930cb --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,7 @@ + + + Types + estate.property.type + list + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1d8a9b321f1..a0880ddeeb9 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,5 @@ - + Porperties estate.property list @@ -23,7 +23,7 @@ - + estate.property.list estate.property @@ -46,12 +46,14 @@
-

- -

+

+
+ + + @@ -75,6 +77,15 @@ + + + + + + + + +
From 8d16b99b5c45fc24d650ca431c482a6602b5f6f1 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Thu, 17 Sep 2026 11:55:52 +0200 Subject: [PATCH 13/17] [IMP] estate: create multiple computed fields for offer and property offer : compute date_deadline according to validity property : compute total_area according to garden_area and living_area compute best_price according to the best offer add on_change to has_garden filling or clearing fields accordingly --- estate/models/estate_property.py | 35 +++++++++++++++++--- estate/models/estate_property_offer.py | 16 ++++++++- estate/views/estate_property_offer_views.xml | 12 ++++--- estate/views/estate_property_views.xml | 2 ++ 4 files changed, 55 insertions(+), 10 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0524f930d58..745349403d7 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 class Property(models.Model): @@ -26,6 +26,7 @@ class Property(models.Model): has_garage = fields.Boolean() has_garden = fields.Boolean() garden_area = fields.Integer() + total_area = fields.Integer(compute='_compute_total_area') garden_orientation = fields.Selection( string='Garden Orientation', selection=[ @@ -49,7 +50,7 @@ class Property(models.Model): required=True, copy=False ) - type_id = fields.Many2one('estate.property.type', string='Type') + type_id = fields.Many2one('estate.property.type') partner_id = fields.Many2one( 'res.partner', string='Buyer', @@ -61,5 +62,31 @@ class Property(models.Model): index=True, default=lambda self: self.env.user ) - tag_ids = fields.Many2many("estate.property.tag", string='Tags') - offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + tag_ids = fields.Many2many('estate.property.tag') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + best_price = fields.Integer(compute='_compute_best_price') + + @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') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price')) + + @api.onchange('has_garden') + def _onchange_hjas_garden(self): + if self.has_garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = '' + return { + 'warning': { + 'title': ('Warning'), + 'message': ('This option erased the fields Garden Area and Garden Orientation') + } + } diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b0385543044..6bffbf29823 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class Offer(models.Model): @@ -20,3 +20,17 @@ class Offer(models.Model): 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') + def _compute_deadline(self): + for record in self: + record.date_deadline = fields.Date.add( + fields.Date.today(), + days=record.validity + ) + + def _inverse_deadline(self): + for record in self: + record.validity = (record.date_deadline - fields.Date.to_date(record.create_date)).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 3c3b05afa6a..4ac8f1a130a 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -6,6 +6,8 @@ + +
@@ -17,11 +19,11 @@
- - - - - + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a0880ddeeb9..8175a1502a6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -59,6 +59,7 @@ + @@ -73,6 +74,7 @@ + From 98ccc2f431732cea762be5899c14553671a381db Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Thu, 17 Sep 2026 13:12:41 +0200 Subject: [PATCH 14/17] [FIX] estate: fix a bug when there is no offer for a property --- estate/models/estate_property.py | 41 ++++++++++++++------------ estate/models/estate_property_offer.py | 6 ++-- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 745349403d7..ae0d93ad507 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -12,13 +12,13 @@ class Property(models.Model): copy=False, default=lambda self: fields.Date.add( fields.Date.today(), - months=3 - ) + months=3, + ), ) expected_price = fields.Float(required=True) selling_price = fields.Float( readonly=True, - copy=False + copy=False, ) bedrooms = fields.Integer(default=2) living_area = fields.Integer() @@ -33,8 +33,8 @@ class Property(models.Model): ('north', 'North'), ('south', 'South'), ('east', 'East'), - ('west', 'West') - ] + ('west', 'West'), + ], ) active = fields.Boolean(default=True) state = fields.Selection( @@ -44,23 +44,23 @@ class Property(models.Model): ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), - ('cancelled', 'Cancelled') + ('cancelled', 'Cancelled'), ], default='new', required=True, - copy=False + copy=False, ) type_id = fields.Many2one('estate.property.type') partner_id = fields.Many2one( 'res.partner', string='Buyer', index=True, - copy=False + copy=False, ) user_id = fields.Many2one('res.users', string='Salesperson', index=True, - default=lambda self: self.env.user + default=lambda self: self.env.user, ) tag_ids = fields.Many2many('estate.property.tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') @@ -74,19 +74,22 @@ def _compute_total_area(self): @api.depends('offer_ids') def _compute_best_price(self): for record in self: - record.best_price = max(record.offer_ids.mapped('price')) + if record.offer_ids: + record.best_price = max(record.offer_ids.mapped('price')) + else: + record.best_price = 0 @api.onchange('has_garden') def _onchange_hjas_garden(self): if self.has_garden: self.garden_area = 10 self.garden_orientation = 'north' - else: - self.garden_area = 0 - self.garden_orientation = '' - return { - 'warning': { - 'title': ('Warning'), - 'message': ('This option erased the fields Garden Area and Garden Orientation') - } - } + return None + self.garden_area = 0 + self.garden_orientation = '' + return { + 'warning': { + 'title': ('Warning'), + 'message': ('This option erased the fields Garden Area and Garden Orientation'), + }, + } diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 6bffbf29823..feb03ce7dc5 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -9,7 +9,7 @@ class Offer(models.Model): status = fields.Selection( selection=[ ('accepted', 'Accepted'), - ('refused', 'Refused') + ('refused', 'Refused'), ], copy=False, ) @@ -17,7 +17,7 @@ class Offer(models.Model): 'res.partner', string='Partner', index=True, - required=True + required=True, ) property_id = fields.Many2one('estate.property', required=True) validity = fields.Integer(default=7) @@ -28,7 +28,7 @@ def _compute_deadline(self): for record in self: record.date_deadline = fields.Date.add( fields.Date.today(), - days=record.validity + days=record.validity, ) def _inverse_deadline(self): From 1dbfe0299e2fcbb6be050684864d3240a35b4447 Mon Sep 17 00:00:00 2001 From: coleo-odoo Date: Thu, 17 Sep 2026 15:12:02 +0200 Subject: [PATCH 15/17] [IMP] estate: create multiple action button to update states of offer and property --- estate/models/estate_property.py | 85 ++++++++++++-------- estate/models/estate_property_offer.py | 39 ++++++--- estate/models/estate_property_tag.py | 4 +- estate/models/estate_property_type.py | 4 +- estate/views/estate_property_offer_views.xml | 3 +- estate/views/estate_property_views.xml | 4 + 6 files changed, 92 insertions(+), 47 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ae0d93ad507..44d901abdc2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,10 @@ from odoo import api, fields, models +from odoo.exceptions import UserError class Property(models.Model): - _name = 'estate.property' - _description = 'Estate property' + _name = "estate.property" + _description = "Estate property" name = fields.Char(required=True) description = fields.Text() @@ -26,70 +27,90 @@ class Property(models.Model): has_garage = fields.Boolean() has_garden = fields.Boolean() garden_area = fields.Integer() - total_area = fields.Integer(compute='_compute_total_area') + total_area = fields.Integer(compute="_compute_total_area") garden_orientation = fields.Selection( - string='Garden Orientation', + string="Garden Orientation", selection=[ - ('north', 'North'), - ('south', 'South'), - ('east', 'East'), - ('west', 'West'), + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), ], ) active = fields.Boolean(default=True) state = fields.Selection( - string='State', + string="State", selection=[ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled'), + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), ], - default='new', + default="new", required=True, copy=False, ) - type_id = fields.Many2one('estate.property.type') + type_id = fields.Many2one("estate.property.type") partner_id = fields.Many2one( - 'res.partner', - string='Buyer', + "res.partner", + string="Buyer", index=True, copy=False, ) - user_id = fields.Many2one('res.users', - string='Salesperson', + user_id = fields.Many2one( + "res.users", + string="Salesperson", index=True, default=lambda self: self.env.user, ) - tag_ids = fields.Many2many('estate.property.tag') - offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') - best_price = fields.Integer(compute='_compute_best_price') + tag_ids = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + best_price = fields.Integer(compute="_compute_best_price") - @api.depends('living_area', 'garden_area') + @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') + @api.depends("offer_ids") def _compute_best_price(self): for record in self: if record.offer_ids: - record.best_price = max(record.offer_ids.mapped('price')) + record.best_price = max(record.offer_ids.mapped("price")) else: record.best_price = 0 - @api.onchange('has_garden') + @api.onchange("has_garden") def _onchange_hjas_garden(self): if self.has_garden: self.garden_area = 10 - self.garden_orientation = 'north' + self.garden_orientation = "north" return None self.garden_area = 0 - self.garden_orientation = '' + self.garden_orientation = "" return { - 'warning': { - 'title': ('Warning'), - 'message': ('This option erased the fields Garden Area and Garden Orientation'), + "warning": { + "title": ("Warning"), + "message": ( + "This option erased the fields Garden Area and Garden Orientation" + ), }, } + + def cancel_property(self): + if self.state == "sold": + msg = "A sold property cannot be cancelled" + raise UserError(msg) + self.state = "cancelled" + return True + + def sell_property(self): + if self.state == "cancelled": + msg = "A cancelled property cannot be sold" + raise UserError(msg) + if not ("accepted" in self.offer_ids.mapped("status")): + msg = "A offer need to be accepted before selling" + raise UserError(msg) + self.state = "sold" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index feb03ce7dc5..25cd85b5f9c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,29 +1,32 @@ from odoo import api, fields, models +from odoo.exceptions import UserError class Offer(models.Model): - _name = 'estate.property.offer' - _description = 'Offer for a property of Estate' + _name = "estate.property.offer" + _description = "Offer for a property of Estate" price = fields.Float(required=True) status = fields.Selection( selection=[ - ('accepted', 'Accepted'), - ('refused', 'Refused'), + ("accepted", "Accepted"), + ("refused", "Refused"), ], copy=False, ) partner_id = fields.Many2one( - 'res.partner', - string='Partner', + "res.partner", + string="Partner", index=True, required=True, ) - property_id = fields.Many2one('estate.property', 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') + date_deadline = fields.Date( + compute="_compute_deadline", inverse="_inverse_deadline", + ) - @api.depends('validity') + @api.depends("validity") def _compute_deadline(self): for record in self: record.date_deadline = fields.Date.add( @@ -33,4 +36,20 @@ def _compute_deadline(self): def _inverse_deadline(self): for record in self: - record.validity = (record.date_deadline - fields.Date.to_date(record.create_date)).days + record.validity = ( + record.date_deadline - fields.Date.to_date(record.create_date) + ).days + + def confirm_offer(self): + if "accepted" in self.property_id.offer_ids.mapped("status"): + msg = "Property already sold" + raise UserError(msg) + self.status = "accepted" + self.property_id.selling_price = self.price + return True + + def refuse_offer(self): + if self.status == "accepted": + self.property_id.selling_price = 0 + self.status = "refused" + return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 2aea838aff3..8646b02bf2f 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -2,7 +2,7 @@ class Type(models.Model): - _name = 'estate.property.tag' - _description = 'Tag of property of Estate' + _name = "estate.property.tag" + _description = "Tag of property of Estate" name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 232058ec35c..47501068306 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -2,7 +2,7 @@ class Type(models.Model): - _name = 'estate.property.type' - _description = 'Type of property of Estate' + _name = "estate.property.type" + _description = "Type of property of Estate" name = fields.Char(required=True) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 4ac8f1a130a..77d5b28685c 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ +