From 44209f16c72467917e34854493693c4d0f9ed15f Mon Sep 17 00:00:00 2001 From: aaboe Date: Mon, 17 Aug 2026 20:15:27 +0200 Subject: [PATCH 01/19] [ADD] Chapter 2: A new Application --- estate/__init__.py | 0 estate/__manifest__.py | 12 ++++++++++++ 2 files changed, 12 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..5b7a0075837 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,12 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': 'Estate', + 'version': '0.1', + 'sequence': 99, + 'summary': 'Estate Management', + 'depends': ['base', 'web'], + 'installable': True, + 'application': True, + 'author': 'Odoo S.A.', + 'license': 'LGPL-3', +} From 5daeedd290c41ba0c29138904fd789f96ffeb5d5 Mon Sep 17 00:00:00 2001 From: aaboe Date: Mon, 17 Aug 2026 23:22:45 +0200 Subject: [PATCH 02/19] [ADD] Chapter 3: Models And Basic Fields --- estate/__init__.py | 1 + estate/models/__init__.py | 3 +++ estate/models/estate_property.py | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+) 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 index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..09b94f90f8d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..3af8de21cad --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,23 @@ + +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = 'estate.property' + _description = 'Estate Property Model' + + 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 09e6b1da30d6c7125e29a45b487d35d40aa37512 Mon Sep 17 00:00:00 2001 From: aaboe Date: Mon, 17 Aug 2026 23:42:34 +0200 Subject: [PATCH 03/19] [ADD] Chapter 4: Security - A Brief Introduction --- estate/__manifest__.py | 8 +++++++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 5b7a0075837..9f06021ac02 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,9 +4,15 @@ 'version': '0.1', 'sequence': 99, 'summary': 'Estate Management', - 'depends': ['base', 'web'], + 'depends': [ + 'base', + 'web' + ], 'installable': True, 'application': True, + 'data': [ + 'security/ir.model.access.csv', + ], 'author': 'Odoo S.A.', 'license': 'LGPL-3', } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /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 +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From bcfa3c9719a4a5125b800b22bd583d282d725292 Mon Sep 17 00:00:00 2001 From: aaboe Date: Tue, 18 Aug 2026 10:13:16 +0200 Subject: [PATCH 04/19] [ADD] Chapter 5: Finally, Some UI To Play With --- estate/__manifest__.py | 4 ++- estate/models/estate_property.py | 34 +++++++++++++++++++++----- estate/views/estate_menus.xml | 10 ++++++++ estate/views/estate_property_views.xml | 10 ++++++++ 4 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9f06021ac02..8b7fe6dc967 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,11 +6,13 @@ 'summary': 'Estate Management', 'depends': [ 'base', - 'web' + 'web', ], 'installable': True, 'application': True, 'data': [ + 'views/estate_property_views.xml', + 'views/estate_menus.xml', 'security/ir.model.access.csv', ], 'author': 'Odoo S.A.', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3af8de21cad..0ceed97cef7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,6 @@ +from dateutil.relativedelta import relativedelta + from odoo import fields, models @@ -6,18 +8,38 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Estate Property Model' + DEFAULT_BEDROOM_COUNT = 2 + DEFAULT_AVAILABILITY_DATE = fields.Date.today() + relativedelta(months=3) + name = fields.Char(required=True) description = fields.Text() - postcode = fields.Char() - date_availability = fields.Date() + active = fields.Boolean() + date_availability = fields.Date(copy=False, default=DEFAULT_AVAILABILITY_DATE) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() - living_area = fields.Integer() + selling_price = fields.Float(readonly=True, copy=False) + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Canceled'), + ], + default='new', + ) + + bedrooms = fields.Integer(default=DEFAULT_BEDROOM_COUNT) 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')], + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ], ) + living_area = fields.Integer() + postcode = fields.Char() diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..b9086edb71d --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..7178e2f725a --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,10 @@ + + + + + Estate Property + estate.property + list,form + + + \ No newline at end of file From b3fccb2e9313d53d7c74859302a9b71d6c07a609 Mon Sep 17 00:00:00 2001 From: aaboe Date: Tue, 18 Aug 2026 13:57:36 +0200 Subject: [PATCH 05/19] [ADD] Chapter 6: Basic Views --- estate/__manifest__.py | 1 + estate/views/estate_basic_views.xml | 88 +++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 estate/views/estate_basic_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 8b7fe6dc967..2d699c34e2d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,7 @@ 'application': True, 'data': [ 'views/estate_property_views.xml', + 'views/estate_basic_views.xml', 'views/estate_menus.xml', 'security/ir.model.access.csv', ], diff --git a/estate/views/estate_basic_views.xml b/estate/views/estate_basic_views.xml new file mode 100644 index 00000000000..fd246029701 --- /dev/null +++ b/estate/views/estate_basic_views.xml @@ -0,0 +1,88 @@ + + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + +
+

+ +

+ +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + +
From a63f85be8c00af13964fad9ca68e2d8fab7093df Mon Sep 17 00:00:00 2001 From: aaboe Date: Tue, 18 Aug 2026 15:08:52 +0200 Subject: [PATCH 06/19] [ADD] Chapter 7: Relations Between Models --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 7 ++++- estate/models/estate_property.py | 8 +++++ estate/models/estate_property_offer.py | 18 +++++++++++ estate/models/estate_property_tag.py | 9 ++++++ estate/models/estate_property_type.py | 9 ++++++ estate/security/ir.model.access.csv | 5 ++- estate/views/estate_basic_views.xml | 12 +++++++- estate/views/estate_menus.xml | 6 ++++ estate/views/estate_property_offer_views.xml | 32 ++++++++++++++++++++ estate/views/estate_property_tags_views.xml | 10 ++++++ estate/views/estate_property_type_views.xml | 10 ++++++ 12 files changed, 126 insertions(+), 3 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_tags_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 2d699c34e2d..c0f668b34da 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,9 @@ 'application': True, 'data': [ 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tags_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_basic_views.xml', 'views/estate_menus.xml', 'security/ir.model.access.csv', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b94f90f8d..a4af1d4d945 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,3 +1,8 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import estate_property +from . import ( + estate_property, + estate_property_offer, + estate_property_tag, + estate_property_type, +) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0ceed97cef7..a2af5180679 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -43,3 +43,11 @@ class EstateProperty(models.Model): ) living_area = fields.Integer() postcode = fields.Char() + + property_type_id = fields.Many2one('estate.property.type', string='Property Type') + salesperson_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) + buyer_id = fields.Many2one('res.partner', string='Buyer') + + 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..d82cdfbc7c1 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,18 @@ + +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Estate Property Offer Model' + + price = fields.Float() + status = fields.Selection( + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + copy=False, + ) + partner_id = fields.Many2one('res.partner', string='Partner', required=True) + property_id = fields.Many2one('estate.property', string='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..e5f19f24322 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,9 @@ + +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Estate Property Tag Model' + + 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..9be8578ef3a --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,9 @@ + +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Estate Property Type Model' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..89f97c50842 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,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_basic_views.xml b/estate/views/estate_basic_views.xml index fd246029701..bbd294b7bdd 100644 --- a/estate/views/estate_basic_views.xml +++ b/estate/views/estate_basic_views.xml @@ -30,11 +30,12 @@

- + + @@ -52,6 +53,15 @@ + + + + + + + + + diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index b9086edb71d..c5706dc76b9 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -5,6 +5,12 @@ + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..49d90574b94 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,32 @@ + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+
+
diff --git a/estate/views/estate_property_tags_views.xml b/estate/views/estate_property_tags_views.xml new file mode 100644 index 00000000000..3844a559d52 --- /dev/null +++ b/estate/views/estate_property_tags_views.xml @@ -0,0 +1,10 @@ + + + + + Estate Property Tags + estate.property.tag + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..17f59e8660d --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,10 @@ + + + + + Estate Property Type + estate.property.type + list,form + + + \ No newline at end of file From 902fca238802b786ccc77b3a1be4d8b8df11fc64 Mon Sep 17 00:00:00 2001 From: aaboe Date: Tue, 18 Aug 2026 21:03:12 +0200 Subject: [PATCH 07/19] [FIX] Address feedback: set string values in model fields --- 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 a2af5180679..c824dfc1eef 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -11,10 +11,10 @@ class EstateProperty(models.Model): DEFAULT_BEDROOM_COUNT = 2 DEFAULT_AVAILABILITY_DATE = fields.Date.today() + relativedelta(months=3) - name = fields.Char(required=True) + name = fields.Char(string='Title', required=True) description = fields.Text() active = fields.Boolean() - date_availability = fields.Date(copy=False, default=DEFAULT_AVAILABILITY_DATE) + date_availability = fields.Date(string='Available From', copy=False, default=DEFAULT_AVAILABILITY_DATE) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) state = fields.Selection( @@ -32,7 +32,7 @@ class EstateProperty(models.Model): facades = fields.Integer() 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'), @@ -41,7 +41,7 @@ class EstateProperty(models.Model): ('west', 'West'), ], ) - living_area = fields.Integer() + living_area = fields.Integer(string='Living Area (sqm)') postcode = fields.Char() property_type_id = fields.Many2one('estate.property.type', string='Property Type') From 0721e5b5a950e2c4d8854f686d68b36fb582c7c8 Mon Sep 17 00:00:00 2001 From: aaboe Date: Tue, 18 Aug 2026 21:03:24 +0200 Subject: [PATCH 08/19] [FIX] Address feedback: merge estate_property views files --- estate/__manifest__.py | 1 - estate/views/estate_basic_views.xml | 98 ---------------------- estate/views/estate_property_views.xml | 109 +++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 107 deletions(-) delete mode 100644 estate/views/estate_basic_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index c0f668b34da..0d6809e5670 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -15,7 +15,6 @@ 'views/estate_property_type_views.xml', 'views/estate_property_tags_views.xml', 'views/estate_property_offer_views.xml', - 'views/estate_basic_views.xml', 'views/estate_menus.xml', 'security/ir.model.access.csv', ], diff --git a/estate/views/estate_basic_views.xml b/estate/views/estate_basic_views.xml deleted file mode 100644 index bbd294b7bdd..00000000000 --- a/estate/views/estate_basic_views.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - - estate.property.list - estate.property - - - - - - - - - - - - - - - - - estate.property.form - estate.property - -
- - -
-

- -

- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
- - - - estate.property.search - estate.property - - - - - - - - - - - - - - - - - - - - - -
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7178e2f725a..b17ade36cc5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,10 +1,103 @@ - - - Estate Property - estate.property - list,form - - - \ No newline at end of file + + + Estate Property + estate.property + list,form + + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + +
+

+ +

+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + From fcaabc195487d58afbe0fde74b7fb3e1838c1d24 Mon Sep 17 00:00:00 2001 From: aaboe Date: Tue, 18 Aug 2026 21:11:58 +0200 Subject: [PATCH 09/19] [FIX] Address feedback: Add newline before EOF --- estate/views/estate_menus.xml | 2 +- estate/views/estate_property_tags_views.xml | 2 +- estate/views/estate_property_type_views.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index c5706dc76b9..6071e71004b 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -13,4 +13,4 @@
- \ No newline at end of file + diff --git a/estate/views/estate_property_tags_views.xml b/estate/views/estate_property_tags_views.xml index 3844a559d52..fadcc70d490 100644 --- a/estate/views/estate_property_tags_views.xml +++ b/estate/views/estate_property_tags_views.xml @@ -7,4 +7,4 @@ list,form - \ No newline at end of file + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 17f59e8660d..1fb4df04260 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -7,4 +7,4 @@ list,form - \ No newline at end of file + From bff75537c204d3b598df12dc08e5e3811472916c Mon Sep 17 00:00:00 2001 From: aaboe Date: Wed, 19 Aug 2026 10:10:17 +0200 Subject: [PATCH 10/19] [ADD] Chapter 8: Computed Fields And Onchanges --- .editorconfig | 44 ++++++++++++++++++++ estate/models/estate_property.py | 26 ++++++++++-- estate/models/estate_property_offer.py | 22 +++++++++- estate/views/estate_property_offer_views.xml | 4 ++ estate/views/estate_property_views.xml | 2 + 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000000..f02b5a97f2a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,44 @@ +# EditorConfig is awesome: https://editorconfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 + + +[*.{xml,xsd}] +max_line_length = off +end_of_line = lf +indent_style = space +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_size = 2 diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c824dfc1eef..60e17830c48 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,7 +1,6 @@ - from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -11,7 +10,7 @@ class EstateProperty(models.Model): DEFAULT_BEDROOM_COUNT = 2 DEFAULT_AVAILABILITY_DATE = fields.Date.today() + relativedelta(months=3) - name = fields.Char(string='Title', required=True) + name = fields.Char(string='Title', required=True, default='New Property') description = fields.Text() active = fields.Boolean() date_availability = fields.Date(string='Available From', copy=False, default=DEFAULT_AVAILABILITY_DATE) @@ -42,6 +41,7 @@ class EstateProperty(models.Model): ], ) living_area = fields.Integer(string='Living Area (sqm)') + total_area = fields.Integer(string='Total Area (sqm)', compute='_compute_total_area') postcode = fields.Char() property_type_id = fields.Many2one('estate.property.type', string='Property Type') @@ -51,3 +51,23 @@ class EstateProperty(models.Model): tag_ids = fields.Many2many('estate.property.tag', string='Tags') offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + best_price = fields.Float(string='Best Offer', 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.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price'), 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 = '' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index d82cdfbc7c1..395db9184d2 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ +from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -16,3 +17,22 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one('res.partner', string='Partner', required=True) property_id = fields.Many2one('estate.property', string='Property', required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date(string='Deadline', compute='_compute_date_deadline', inverse='_inverse_date_deadline') + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for record in self: + create_date = fields.Date.today() + if record.create_date: + create_date = record.create_date.date() + + record.date_deadline = create_date + relativedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + create_date = fields.Date.today() + if record.create_date: + create_date = record.create_date.date() + + record.validity = (record.date_deadline - create_date).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 49d90574b94..0cbd0753d9e 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + +
@@ -22,6 +24,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index b17ade36cc5..ee28e501c38 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -43,6 +43,7 @@ + @@ -56,6 +57,7 @@ + From 228b38626218afa04b4ffe7f96b45cd79f48bfe5 Mon Sep 17 00:00:00 2001 From: aaboe Date: Wed, 19 Aug 2026 13:21:32 +0200 Subject: [PATCH 11/19] [ADD] Chapter 9: Ready For Some Action? --- estate/models/estate_property.py | 13 +++++++++++++ estate/models/estate_property_offer.py | 17 +++++++++++++++++ estate/views/estate_property_offer_views.xml | 10 ++++++++-- estate/views/estate_property_views.xml | 17 +++++++++++------ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 60e17830c48..ff6bb2ebf22 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -71,3 +72,15 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = '' + + def action_sold(self): + CANNOT_SELL_CANCELED_PROPERTY = "You cannot sell a canceled property" + if self.state == 'canceled': + raise UserError(CANNOT_SELL_CANCELED_PROPERTY) + self.state = 'sold' + + def action_cancel(self): + CANNOT_SELL_SOLD_PROPERTY = "You cannot cancel a sold property" + if self.state == 'sold': + raise UserError(CANNOT_SELL_SOLD_PROPERTY) + self.state = 'canceled' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 395db9184d2..bfc54e28cb6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -36,3 +37,19 @@ def _inverse_date_deadline(self): create_date = record.create_date.date() record.validity = (record.date_deadline - create_date).days + + def action_accept(self): + CANNOT_ACCEPT_ACCEPTED_OFFER = "You cannot accept an offer for a property that already has an accepted offer" + if self.property_id.state == 'offer_accepted': + raise UserError(CANNOT_ACCEPT_ACCEPTED_OFFER) + + self.status = 'accepted' + self.property_id.state = 'offer_accepted' + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id + + def action_refuse(self): + CANNOT_REFUSE_ACCEPTED_OFFER = "You cannot refuse an accepted offer" + if self.status == 'accepted': + raise UserError(CANNOT_REFUSE_ACCEPTED_OFFER) + self.status = 'refused' diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 0cbd0753d9e..06b13483fc2 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -10,7 +10,9 @@ - + + + + + + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f66deb07a6f..3ef9ffe5226 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,6 +5,7 @@ Estate Property estate.property list,form + {'search_default_available': 1} @@ -12,14 +13,18 @@ estate.property.list estate.property - + - + + @@ -31,8 +36,11 @@
-
@@ -40,13 +48,12 @@

- +
- - + @@ -61,13 +68,13 @@ - + - + - + @@ -92,7 +99,7 @@ - + @@ -102,7 +109,7 @@ name="date_availability" string="Available" domain="[('date_availability', '>=;', 'today')]" /> --> - + From a33c67f41d27c38d035eaedc2371ff270062c53a Mon Sep 17 00:00:00 2001 From: aaboe Date: Thu, 20 Aug 2026 14:40:39 +0200 Subject: [PATCH 14/19] [ADD] Chapter 12: Inheritance --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 6 ++++++ estate/models/estate_property_offer.py | 8 ++++++++ estate/models/res_users.py | 12 ++++++++++++ estate/views/estate_property_views.xml | 1 + estate/views/res_users_views.xml | 15 +++++++++++++++ 7 files changed, 44 insertions(+) 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 1405e7cccda..0d852d78797 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -15,6 +15,7 @@ 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tags_views.xml', + 'views/res_users_views.xml', 'views/estate_menus.xml', 'security/ir.model.access.csv', ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index a4af1d4d945..3ced267895e 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -5,4 +5,5 @@ estate_property_offer, estate_property_tag, estate_property_type, + res_users, ) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e73d9b6f1d8..85a93c63c8e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -98,3 +98,9 @@ def _check_selling_price(self): for record in self: if float_compare(record.selling_price, record.expected_price * 0.9, precision_digits=2) == -1: raise UserError(SELLING_PRICE_MUST_BE_AT_LEAST_90_PERCENT_OF_EXPECTED_PRICE) + + @api.ondelete(at_uninstall=False) + def _unlink_if_not_new_or_canceled(self): + YOU_CANNOT_DELETE_A_PROPERTY_THAT_IS_NOT_NEW_OR_CANCELED = 'You cannot delete a property that is not new or canceled' + if any(record.state not in ('new', 'canceled') for record in self): + raise UserError(YOU_CANNOT_DELETE_A_PROPERTY_THAT_IS_NOT_NEW_OR_CANCELED) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8f51898e359..b30390209f3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -60,3 +60,11 @@ def action_refuse(self): if self.status == 'accepted': raise UserError(CANNOT_REFUSE_ACCEPTED_OFFER) self.status = 'refused' + + @api.model + def create(self, vals_list): + for vals in vals_list: + property_id = self.env['estate.property'].browse(vals['property_id']) + property_id.state = 'offer_received' + + return super().create(vals_list) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..49f1de44380 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many( + 'estate.property', + 'salesperson_id', + string='Real Estate Properties', + domain=[('state', 'in', ['new', 'offer_received'])], + ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3ef9ffe5226..eaa6ee106e4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -18,6 +18,7 @@ decoration-bf="state == 'offer_accepted'" decoration-muted="state == 'sold'"> + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..82f4b628450 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,15 @@ + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + From 933bee27393cfb907f3e8f8816b55cda923e39eb Mon Sep 17 00:00:00 2001 From: aaboe Date: Thu, 20 Aug 2026 15:19:42 +0200 Subject: [PATCH 15/19] [FIX] CI Faliure --- estate/views/estate_property_offer_views.xml | 2 +- estate/views/estate_property_views.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 97b15ae9a58..73782a6c2f5 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -20,7 +20,7 @@