From b68a19249d490da35c2c6fd6b04da5f28ef87d99 Mon Sep 17 00:00:00 2001 From: Ait-Mlouk Omar Date: Wed, 24 Sep 2025 22:12:01 +0000 Subject: [PATCH 01/36] [FIX] website_airproof: fix template loading order issue The carousel template file was loaded too late in the manifest data sequence, causing a ParseError when new_page_templates tried to reference it. closes odoo/tutorials#988 X-original-commit: 34d70b5f1c1079bdb9ae64d3fb2f95c95d6cba22 Signed-off-by: Antoine Vandevenne (anv) --- website_airproof/__manifest__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website_airproof/__manifest__.py b/website_airproof/__manifest__.py index f6cd9dc0d5e..2c2c62d6a18 100644 --- a/website_airproof/__manifest__.py +++ b/website_airproof/__manifest__.py @@ -7,6 +7,9 @@ 'license': 'LGPL-3', 'depends': ['website_sale', 'website_sale_wishlist', 'website_blog', 'website_mass_mailing'], 'data': [ + # Snippets + 'views/snippets/options.xml', + 'views/snippets/s_airproof_carousel.xml', # Options 'data/presets.xml', 'data/website.xml', @@ -24,9 +27,6 @@ 'views/website_templates.xml', 'views/website_sale_templates.xml', 'views/website_sale_wishlist_templates.xml', - # Snippets - 'views/snippets/options.xml', - 'views/snippets/s_airproof_carousel.xml', # Images 'data/images.xml', ], From b7e18c9e94f7ba6e0fb293f050cb76001344d70c Mon Sep 17 00:00:00 2001 From: "Claire (clbr)" Date: Fri, 21 Nov 2025 14:02:18 +0100 Subject: [PATCH 02/36] [FIX] awesome_owl: Fix missing css variables Some imports are missing in the manifest causing some warnings and the css doesn't load properly. Fix was fixed in the master branch, backporting it in 19.0 for the onboarding classes that always happen in the lastest stable. task-none Part-of: odoo/tutorials#1037 Signed-off-by: Antoine Vandevenne (anv) --- awesome_owl/__manifest__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index e8ac1cda552..55002ab81de 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -29,8 +29,10 @@ 'assets': { 'awesome_owl.assets_playground': [ ('include', 'web._assets_helpers'), + ('include', 'web._assets_backend_helpers'), 'web/static/src/scss/pre_variables.scss', 'web/static/lib/bootstrap/scss/_variables.scss', + 'web/static/lib/bootstrap/scss/_maps.scss', ('include', 'web._assets_bootstrap'), ('include', 'web._assets_core'), 'web/static/src/libs/fontawesome/css/font-awesome.css', From 781b5900205cfd7fff9aa3a03ee85b76ad535107 Mon Sep 17 00:00:00 2001 From: "Claire (clbr)" Date: Fri, 21 Nov 2025 14:18:08 +0100 Subject: [PATCH 03/36] [FIX] awesome_dashboard: Get rid of the deprecated warning `json` routes were deprecated to `jsonrpc` in 19.0, let's get rid of the warning to avoid confusion for the newdoos. task-none closes odoo/tutorials#1037 Signed-off-by: Antoine Vandevenne (anv) --- awesome_dashboard/controllers/controllers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awesome_dashboard/controllers/controllers.py b/awesome_dashboard/controllers/controllers.py index 56d4a051287..05977d3bd7f 100644 --- a/awesome_dashboard/controllers/controllers.py +++ b/awesome_dashboard/controllers/controllers.py @@ -9,7 +9,7 @@ logger = logging.getLogger(__name__) class AwesomeDashboard(http.Controller): - @http.route('/awesome_dashboard/statistics', type='json', auth='user') + @http.route('/awesome_dashboard/statistics', type='jsonrpc', auth='user') def get_statistics(self): """ Returns a dict of statistics about the orders: From bc75b9185cb5b4bbc36a8da8ea0574cefb32a460 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 10:47:04 +0200 Subject: [PATCH 04/36] [ADD] estate: initialize new application --- awesome_dashboard/estate/__init__.py | 0 awesome_dashboard/estate/__manifest__.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 awesome_dashboard/estate/__init__.py create mode 100644 awesome_dashboard/estate/__manifest__.py diff --git a/awesome_dashboard/estate/__init__.py b/awesome_dashboard/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/awesome_dashboard/estate/__manifest__.py b/awesome_dashboard/estate/__manifest__.py new file mode 100644 index 00000000000..0537cab4ab8 --- /dev/null +++ b/awesome_dashboard/estate/__manifest__.py @@ -0,0 +1,8 @@ +{ + 'name': "Estate", + 'version': '1.0', + 'depends': ['base'], + 'category': 'Tutorials', + 'application': True + +} \ No newline at end of file From 703b451b51778a4e04d2c5b5d70ff74b35fdd925 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 10:58:41 +0200 Subject: [PATCH 05/36] [MOV] estate: move module to root --- {awesome_dashboard/estate => estate}/__init__.py | 0 {awesome_dashboard/estate => estate}/__manifest__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {awesome_dashboard/estate => estate}/__init__.py (100%) rename {awesome_dashboard/estate => estate}/__manifest__.py (100%) diff --git a/awesome_dashboard/estate/__init__.py b/estate/__init__.py similarity index 100% rename from awesome_dashboard/estate/__init__.py rename to estate/__init__.py diff --git a/awesome_dashboard/estate/__manifest__.py b/estate/__manifest__.py similarity index 100% rename from awesome_dashboard/estate/__manifest__.py rename to estate/__manifest__.py From 2b46f88c3fcb9282d5942435d695341c61848ccc Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 11:38:55 +0200 Subject: [PATCH 06/36] [ADD] estate: add property model and fields --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 22 ++++++++++++++++++++++ 3 files changed, 24 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..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..832f7280bc2 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,22 @@ +from odoo import fields,models + +class EstatePropertytModel(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( + string='Type', + selection=[('north', 'North'), ('south', 'South'), ('east','East'), ('west','West')],) + From 2666fbd259bc6b79be738591d2e22fa67506d692 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 13:37:43 +0200 Subject: [PATCH 07/36] [CLN] estate: fix formatting and typos --- estate/__init__.py | 2 +- estate/__manifest__.py | 3 +-- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 11 ++++++----- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 0537cab4ab8..62c2a2b3f5d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,8 +1,7 @@ { - 'name': "Estate", + 'name': "Estate", 'version': '1.0', 'depends': ['base'], 'category': 'Tutorials', '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 832f7280bc2..d0b903c6931 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ -from odoo import fields,models +from odoo import fields, models + class EstatePropertytModel(models.Model): _name = "estate_property" @@ -7,8 +8,8 @@ class EstatePropertytModel(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability= fields.Date() - expected_price= fields.Float(required=True) + date_availability = fields.Date() + expected_price = fields.Float(required=True) selling_price = fields.Float() bedrooms = fields.Integer() living_area = fields.Integer() @@ -18,5 +19,5 @@ class EstatePropertytModel(models.Model): 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')]) + \ No newline at end of file From a1c6e1e2bb20777403fb712c6b8c6621885d43a3 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 14:06:54 +0200 Subject: [PATCH 08/36] [ADD] estate: add access rights for property model --- estate/__manifest__.py | 5 ++++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 62c2a2b3f5d..1f0dc1cd92a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,5 +3,8 @@ 'version': '1.0', 'depends': ['base'], 'category': 'Tutorials', - 'application': True + 'application': True, + 'data' : ['security/ir.model.access.csv'], + 'author': "Odoo", + 'license': 'AGPL-3' } \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..ab63520e22b --- /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 1fb914a77c38cda4388641886b68b8510e3152fe Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 14:21:36 +0200 Subject: [PATCH 09/36] [CLN] estate: fix formatting and follow odoo guidelines --- estate/models/estate_property.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d0b903c6931..daa3d2dc880 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -19,5 +19,6 @@ class EstatePropertytModel(models.Model): 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')] + ) \ No newline at end of file From afbfbba22ea5a09c57de80f1db8a6c08c66b1ce8 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 14:30:56 +0200 Subject: [PATCH 10/36] [CLN] estate: fix formatting and follow odoo guidelines --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 1f0dc1cd92a..4fc737abb0c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,7 +4,7 @@ 'depends': ['base'], 'category': 'Tutorials', 'application': True, - 'data' : ['security/ir.model.access.csv'], + 'data': ['security/ir.model.access.csv'], 'author': "Odoo", 'license': 'AGPL-3' } \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index daa3d2dc880..d34e032ac6c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -21,4 +21,3 @@ class EstatePropertytModel(models.Model): string='Type', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] ) - \ No newline at end of file From 80d74cea8eb98a641ff6e45849933cc799927672 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 14:31:59 +0200 Subject: [PATCH 11/36] [CLN] estate: fix formatting and follow odoo guidelines --- estate/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4fc737abb0c..19de7d7ee52 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,4 +7,4 @@ 'data': ['security/ir.model.access.csv'], 'author': "Odoo", 'license': 'AGPL-3' -} \ No newline at end of file +} From 37bbff7a5e543a537f3307bddec58a1801ceb370 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 22 Apr 2026 16:23:35 +0200 Subject: [PATCH 12/36] [ADD] estate: add basic UI and field business logic The goal of this commit is to provide the initial user interface and core field behaviors for the Real Estate module. - UI: Added window action and a three-level menu hierarchy to allow users to navigate to property records. - Data Integrity: Set 'selling_price' to read-only and prevented copying of 'date_availability' to ensure fresh data on duplication. - Automation: Implemented a 3-month default for 'date_availability' using a dynamic lambda to handle the 'today' calculation at runtime. - Management: Added 'active' (archiving) and 'state' (workflow) fields to support record lifecycle management. --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 19 ++++++++++++++++--- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 7 +++++++ 4 files changed, 31 insertions(+), 4 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 19de7d7ee52..38b25809f2d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,7 +4,7 @@ 'depends': ['base'], 'category': 'Tutorials', 'application': True, - 'data': ['security/ir.model.access.csv'], + 'data': ['security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menus.xml'], 'author': "Odoo", 'license': 'AGPL-3' } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d34e032ac6c..15f687330e1 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,10 +8,10 @@ class EstatePropertytModel(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(copy=False, default=lambda self: 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() garage = fields.Boolean() @@ -21,3 +21,16 @@ class EstatePropertytModel(models.Model): string='Type', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] ) + active = fields.Boolean(default=True) + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Cancelled'), + ], + required=True, + copy=False, + default='new', +) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..780dc9d2789 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..76041020552 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate_property + list,form + + From 39cd9c60ca42d80790196ff04174d81de03107dd Mon Sep 17 00:00:00 2001 From: "David Van Droogenbroeck (DROD)" Date: Thu, 23 Apr 2026 10:01:46 +0200 Subject: [PATCH 13/36] [FIX] estate: remove unlink permission for base users Base users were able to unlink records although they're just plebs. --- estate/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index ab63520e22b..7fe74a60dbf 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 +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 a930b33246285a10a5af74f0a12c82d6d1d80a15 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Thu, 23 Apr 2026 13:22:33 +0200 Subject: [PATCH 14/36] [IMP] estate: add custom views and address PR feedback Implement the property list, form, and search views, while correcting technical naming and styling issues. - Add list view for property overview - Add form view with sheet layout, groups, and notebook - Add search view with 'Available' filter and 'Postcode' grouping - Correct model name to 'estate.property' and fix field indentation --- estate/__manifest__.py | 10 +++- estate/models/estate_property.py | 15 +++-- estate/security/ir.model.access.csv | 2 +- estate/views/estate_property_views.xml | 77 +++++++++++++++++++++++++- 4 files changed, 95 insertions(+), 9 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 38b25809f2d..a494be84a76 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,16 @@ { 'name': "Estate", 'version': '1.0', - 'depends': ['base'], + 'depends': [ + 'base', + ], 'category': 'Tutorials', 'application': True, - 'data': ['security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menus.xml'], + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + ], 'author': "Odoo", 'license': 'AGPL-3' } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 15f687330e1..daa3121c8b2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,7 +2,7 @@ class EstatePropertytModel(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "Estate Property Model" name = fields.Char(required=True) @@ -18,9 +18,14 @@ class EstatePropertytModel(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Type', - selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] - ) + string='Type', + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ] + ) active = fields.Boolean(default=True) state = fields.Selection( selection=[ @@ -33,4 +38,4 @@ class EstatePropertytModel(models.Model): required=True, copy=False, default='new', -) + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 7fe74a60dbf..d7b48ea1286 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,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,0 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 76041020552..238e00db194 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,7 +1,82 @@ Properties - estate_property + estate.property list,form + + estate.property.list + estate.property + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + estate.property.search + estate.property + + + + + + + + + + + + + + + + + +
From b9b84e2ab7a565a0aa2eeb9ee6a7501a75684243 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Thu, 23 Apr 2026 18:17:44 +0200 Subject: [PATCH 15/36] [ADD] estate: add relational models and fields - Create estate.property.type, estate.property.tag, and estate.property.offer models. - Add Many2one relationships for property type, salesperson (res.users), and buyer (res.partner). - Add Many2many relationship for property tags with many2many_tags widget. - Add One2many relationship for property offers. - Update views to include new relational fields and notebook pages. - Add security access rights for all new models. --- estate/__manifest__.py | 5 +- estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 22 ++++++++ estate/models/estate_property_offer.py | 24 +++++++++ estate/models/estate_property_tag.py | 7 +++ estate/models/estate_property_type.py | 7 +++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 4 ++ estate/views/estate_property_offer_views.xml | 27 ++++++++++ estate/views/estate_property_tag_views.xml | 7 +++ estate/views/estate_property_type_views.xml | 7 +++ estate/views/estate_property_views.xml | 53 ++++++++++++-------- k | 6 +++ 13 files changed, 153 insertions(+), 22 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 create mode 100644 k diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a494be84a76..9ed92e60842 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,7 +8,10 @@ 'application': True, 'data': [ 'security/ir.model.access.csv', - 'views/estate_property_views.xml', + '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", 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 daa3121c8b2..aa4c5545cae 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -39,3 +39,25 @@ class EstatePropertytModel(models.Model): copy=False, default='new', ) + type_id = fields.Many2one( + "estate.property.type", + string="Tag", + ) + salesperson_id = fields.Many2one( + "res.users", + string="Salesperson", + default=lambda self: self.env.user, + ) + buyer_id = fields.Many2one( + "res.partner", + string="Buyer", + copy=False, + ) + 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..b24ff662d0c --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,24 @@ +from odoo import fields, models + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + 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..b8e25613fa2 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real Estate Property Tag" + + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..769a9f94a13 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real Estate Property Type" + + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d7b48ea1286..ba33af09d50 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,0 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,0 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,0 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,0 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 780dc9d2789..5c12f793fe0 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -3,5 +3,9 @@ + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..99f829c2e10 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,27 @@ + + + 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..091876df866 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,7 @@ + + + Property Tag + 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..87fb9e76b6b --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,7 @@ + + + Property Type + estate.property.type + list,form + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 238e00db194..5a761b4f379 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -29,9 +29,11 @@

+ + @@ -53,30 +55,39 @@ + + + + + + + + + - - estate.property.search - estate.property - - - - - - - - - - - - - - - - - - + diff --git a/k b/k new file mode 100644 index 00000000000..ebce8f24bfe --- /dev/null +++ b/k @@ -0,0 +1,6 @@ + id | create_uid | write_uid | create_date | write_date | bedrooms | living_area | facades | garden_area | name | postcode | garden_orientation | date_availability | description | garage | garden | expected_price | selling_price | active | state +----+------------+-----------+----------------------------+----------------------------+----------+-------------+---------+-------------+-------+----------+--------------------+-------------------+-------------+--------+--------+----------------+---------------+--------+------- + 4 | 2 | 2 | 2026-04-22 15:27:35.807356 | 2026-04-22 15:27:35.807356 | 2 | 0 | 0 | 0 | sdgsg | | | 2026-07-22 | | f | f | 0 | | t | new + 3 | 2 | 2 | 2026-04-22 14:59:56.16026 | 2026-04-23 10:00:36.637113 | 2 | 0 | 0 | 0 | asf | 1244124 | | 2026-07-22 | | t | t | 22222 | | t | new +(2 rows) + From 034623c5b4217930b498ab499c1c4d39d45bce9a Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Fri, 24 Apr 2026 09:22:49 +0200 Subject: [PATCH 16/36] [FIX] estate: fix invalid search view definition --- estate/views/estate_property_views.xml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5a761b4f379..e84fb97da01 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -69,7 +69,7 @@ - + From 688ae48ecd0bd73627c618db3ff516b201155018 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Fri, 24 Apr 2026 10:55:07 +0200 Subject: [PATCH 17/36] [IMP] estate: add computed fields and onchange logic - Add 'total_area' as a sum of living and garden area. - Add 'best_price' to track the highest offer using mapped(). - Add 'date_deadline' and 'validity' to offers with an inverse function to allow bi-directional updates. - Implement an onchange to automatically set garden area and orientation when the garden checkbox is toggled. --- estate/models/estate_property.py | 25 +++++++++++++++++++- estate/models/estate_property_offer.py | 20 +++++++++++++++- estate/views/estate_property_offer_views.xml | 4 ++++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index aa4c5545cae..8f8c6a69dbd 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 EstatePropertytModel(models.Model): @@ -61,3 +61,26 @@ class EstatePropertytModel(models.Model): "property_id", string="Offers" ) + total_area = fields.Integer(compute="_compute_total_area") + best_price = fields.Float(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 or 0) + (record.garden_area or 0) + @api.depends("offer_ids") + def _compute_best_price(self): + for record in self: + highest_price = 0 + for offer in record.offer_ids: + if not offer.status: + highest_price = max(highest_price,offer.price) + record.best_price = highest_price + @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 b24ff662d0c..2d4140f5f50 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 EstatePropertyOffer(models.Model): _name = "estate.property.offer" @@ -22,3 +22,21 @@ class EstatePropertyOffer(models.Model): string="Property", required=True, ) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + ) + + @api.depends("validity","create_date") + def _compute_date_deadline(self): + for record in self: + record.date_deadline = fields.Date.add( + (fields.Date.to_date(record.create_date) or fields.Date.today()), + days=record.validity + ) + def _inverse_date_deadline(self): + for record in self: + record.validity = ( + record.date_deadline - (fields.Date.to_date(record.create_date) or fields.Date.today()) + ).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 99f829c2e10..9ca2ae40302 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -6,6 +6,8 @@ + + @@ -19,6 +21,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index e84fb97da01..ab5f028f0e0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -39,6 +39,7 @@ + @@ -53,6 +54,7 @@ + From f0738122407d05d1977e88e52e58e1e1293af786 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Fri, 24 Apr 2026 11:18:02 +0200 Subject: [PATCH 18/36] [FIX] estate: fix python linting and styling issues --- estate/models/estate_property.py | 22 ++++++++++++---------- estate/models/estate_property_offer.py | 2 ++ estate/models/estate_property_tag.py | 2 +- estate/models/estate_property_type.py | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8f8c6a69dbd..c4c7c70f05b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -20,7 +20,7 @@ class EstatePropertytModel(models.Model): garden_orientation = fields.Selection( string='Type', selection=[ - ('north', 'North'), + ('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West'), @@ -40,7 +40,7 @@ class EstatePropertytModel(models.Model): default='new', ) type_id = fields.Many2one( - "estate.property.type", + "estate.property.type", string="Tag", ) salesperson_id = fields.Many2one( @@ -57,30 +57,32 @@ class EstatePropertytModel(models.Model): "estate.property.tag", string="Tags", ) offer_ids = fields.One2many( - "estate.property.offer", - "property_id", + "estate.property.offer", + "property_id", string="Offers" ) total_area = fields.Integer(compute="_compute_total_area") best_price = fields.Float(compute="_compute_best_price") - @api.depends("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 or 0) + (record.garden_area or 0) + @api.depends("offer_ids") def _compute_best_price(self): for record in self: highest_price = 0 for offer in record.offer_ids: if not offer.status: - highest_price = max(highest_price,offer.price) + highest_price = max(highest_price, offer.price) record.best_price = highest_price + @api.onchange("garden") def _onchange_garden(self): if self.garden: - self.garden_area = 10 - self.garden_orientation = 'north' + self.garden_area = 10 + self.garden_orientation = 'north' else: - self.garden_area = 0 - self.garden_orientation = '' + self.garden_area = 0 + self.garden_orientation = '' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 2d4140f5f50..4e6e1f9bce8 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from odoo import api, fields, models + class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Real Estate Property Offer" @@ -35,6 +36,7 @@ def _compute_date_deadline(self): (fields.Date.to_date(record.create_date) or fields.Date.today()), days=record.validity ) + def _inverse_date_deadline(self): for record in self: record.validity = ( diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index b8e25613fa2..0fcc469b18d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -4,4 +4,4 @@ class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Real Estate Property Tag" - name = fields.Char(required=True) \ No newline at end of file + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 769a9f94a13..f8e0ef9822d 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -4,4 +4,4 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Real Estate Property Type" - name = fields.Char(required=True) \ No newline at end of file + name = fields.Char(required=True) From a8f5d11b473f80b9fa98c2d00692890437ff0d86 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Fri, 24 Apr 2026 11:19:06 +0200 Subject: [PATCH 19/36] [FIX] estate: fix python linting and styling issues --- estate/__manifest__.py | 2 +- estate/models/estate_property_offer.py | 10 +++++----- estate/models/estate_property_tag.py | 1 + estate/models/estate_property_type.py | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9ed92e60842..2885b521e98 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,7 +7,7 @@ 'category': 'Tutorials', 'application': True, 'data': [ - 'security/ir.model.access.csv', + 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4e6e1f9bce8..7afa9bb71e4 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -19,21 +19,21 @@ class EstatePropertyOffer(models.Model): required=True ) property_id = fields.Many2one( - "estate.property", - string="Property", + "estate.property", + string="Property", required=True, ) validity = fields.Integer(default=7) date_deadline = fields.Date( - compute="_compute_date_deadline", + compute="_compute_date_deadline", inverse="_inverse_date_deadline", ) - @api.depends("validity","create_date") + @api.depends("validity", "create_date") def _compute_date_deadline(self): for record in self: record.date_deadline = fields.Date.add( - (fields.Date.to_date(record.create_date) or fields.Date.today()), + (fields.Date.to_date(record.create_date) or fields.Date.today()), days=record.validity ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 0fcc469b18d..198c1037c41 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,5 +1,6 @@ from odoo import fields, models + class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Real Estate Property Tag" diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index f8e0ef9822d..ced40ef01cc 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,5 +1,6 @@ from odoo import fields, models + class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Real Estate Property Type" From 11467ced63a508c2197887608542a537d65cbaf2 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Fri, 24 Apr 2026 13:25:27 +0200 Subject: [PATCH 20/36] [IMP] estate: add state management and offer actions - Add 'Sold' and 'Cancel' buttons to property form with UserError constraints to prevent invalid state transitions. - Add 'Accept' and 'Refuse' buttons to property offers with icons. - Implement logic to automatically set the property buyer and selling price when an offer is accepted. - Add validation to ensure only one offer can be accepted per property. --- estate/models/estate_property.py | 20 ++++++++++++++++---- estate/models/estate_property_offer.py | 17 +++++++++++++++-- estate/views/estate_property_offer_views.xml | 6 ++++-- estate/views/estate_property_views.xml | 5 +++++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c4c7c70f05b..f58471d7e2e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import api, fields, models, exceptions class EstatePropertytModel(models.Model): @@ -33,7 +33,7 @@ class EstatePropertytModel(models.Model): ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), - ('canceled', 'Cancelled'), + ('cancelled', 'Cancelled'), ], required=True, copy=False, @@ -68,7 +68,7 @@ class EstatePropertytModel(models.Model): def _compute_total_area(self): for record in self: record.total_area = (record.living_area or 0) + (record.garden_area or 0) - + @api.depends("offer_ids") def _compute_best_price(self): for record in self: @@ -77,7 +77,7 @@ def _compute_best_price(self): if not offer.status: highest_price = max(highest_price, offer.price) record.best_price = highest_price - + @api.onchange("garden") def _onchange_garden(self): if self.garden: @@ -86,3 +86,15 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = '' + + def action_sold(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError("cancelled prop can't be sold") + record.state = 'sold' + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError("Sold prop can't be cancelled") + record.state = 'cancelled' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7afa9bb71e4..65e75b0fefd 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import api, fields, models, exceptions class EstatePropertyOffer(models.Model): @@ -15,7 +15,7 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one( "res.partner", - string="Partner", + string="Partner", required=True ) property_id = fields.Many2one( @@ -42,3 +42,16 @@ def _inverse_date_deadline(self): record.validity = ( record.date_deadline - (fields.Date.to_date(record.create_date) or fields.Date.today()) ).days + + def action_accept_offer(self): + for record in self: + if record.property_id.state == 'sold': + raise exceptions.UserError("Prob is already sold") + record.property_id.state = 'sold' + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + record.status = 'accepted' + + def action_refuse_offer(self): + for record in self: + record.status = 'refused' diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 9ca2ae40302..4d8b9949b72 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -6,8 +6,10 @@ - - + + + + +
+

+ +

+
+ + + + + + + + + + + + + +
+ \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2f541bf1d57..9bd61de8011 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,19 +3,25 @@ Properties estate.property list,form + {'search_default_available': True} estate.property.list estate.property - + - + + @@ -25,20 +31,21 @@
-

- +
- + @@ -57,13 +64,13 @@ - - + + - + @@ -85,13 +92,13 @@ - + - + From c6546c2411df0bf50d9d51f084c3a8986a7baeca Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Mon, 27 Apr 2026 16:51:05 +0200 Subject: [PATCH 24/36] [CLN] estate: cleanup Python code and remove debug prints - Remove trailing whitespaces in property and offer models. - Remove 'print' statement in property type compute method. - Fix blank lines containing whitespace. - Clean up formatting in ValidationErrors and related fields. --- estate/models/estate_property.py | 2 +- estate/models/estate_property_offer.py | 6 +++--- estate/models/estate_property_type.py | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ada430003c9..3665ac68dc6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -120,4 +120,4 @@ def _check_price(self): continue limit = record.expected_price * 0.9 if float_compare(record.selling_price, limit, precision_digits=2) == -1: - raise ValidationError("The selling price cannot be lower than 90% of the expected price!") + raise ValidationError("The selling price cannot be lower than 90% of the expected price!") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index c49853f0ce0..0c39bcc3b83 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -25,8 +25,8 @@ class EstatePropertyOffer(models.Model): required=True, ) property_type_id = fields.Many2one( - related="property_id.type_id", - string="Property Type", + related="property_id.type_id", + string="Property Type", store=True ) validity = fields.Integer(default=7) @@ -72,5 +72,5 @@ def create(self, vals_list): for offer in offers: if offer.property_id.state == 'new': offer.property_id.state = 'offer_received' - + return offers diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 36ceaa70194..491291c8e5c 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -15,5 +15,4 @@ class EstatePropertyType(models.Model): @api.depends("offer_ids") def _compute_offer_count(self): for record in self: - print("what ever", len(record.offer_ids)) record.offer_count = len(record.offer_ids) From 4cec6242e060f18cf393c62c9bccb6eb4a35e047 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Mon, 27 Apr 2026 16:59:30 +0200 Subject: [PATCH 25/36] [CLN] estate: fix trailing whitespace in validation error --- estate/models/estate_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3665ac68dc6..e1d5f277624 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -120,4 +120,4 @@ def _check_price(self): continue limit = record.expected_price * 0.9 if float_compare(record.selling_price, limit, precision_digits=2) == -1: - raise ValidationError("The selling price cannot be lower than 90% of the expected price!") + raise ValidationError("The selling price cannot be lower than 90% of the expected price!") From 7df65a3d63dff0e5b6a15c4331b6ff06e2be3d98 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Mon, 27 Apr 2026 17:24:11 +0200 Subject: [PATCH 26/36] [CLN] estate: remove redundant invisible status field from offer list --- estate/views/estate_property_offer_views.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 4cbf48d8abc..232e642bf7f 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -19,7 +19,6 @@ - -
-

- -

-
- - - - - - - - - - - -
- -
+ icon="fa-money" + > + + + +
+

+ +

+
+ + + + + + + + + + + + + +
- \ No newline at end of file + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 81f3a45bd0a..fe6c10c8fd4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,127 +5,128 @@ list,form,kanban {'search_default_available': True} + - estate.property.list - estate.property - - - - - - - - - - - - - - - estate.property.form - estate.property - -
-
-
- -
-

- -

- -
- - - - - - - - + estate.property.list + estate.property + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
+ + +
+
+ + + + estate.property.form + estate.property + +
+
+
+ +
+

+ +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ - estate.property.search - estate.property - - - - - - - - - - - - - - - + estate.property.search + estate.property + + + + + + + + + + + + + + - estate.property.kanban - estate.property - - - - - -
- + estate.property.kanban + estate.property + + + + +
- Expected Price: -
-
- Best Price: + +
+ Expected Price: +
+
+ Best Price: +
+
+ Selling Price: +
+
-
- Selling Price: -
- -
-
-
-
-
+ + + +
diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index e19f47003b7..cfc9f1060cc 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -1,6 +1,6 @@ { 'name': "Estate Account", - 'version': '1.0', + 'version': '19.0.0.1.0', 'depends': [ 'estate', 'account' diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index d2b24c29d16..afbf8a20263 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, Command +from odoo import Command, models class EstateProperty(models.Model): From 40e8c45cbcc15eecce22164a75d635e83311fc5d Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 29 Apr 2026 15:01:05 +0200 Subject: [PATCH 34/36] [REF] estate: fix whitespace and PEP8 linting violations --- estate/models/estate_property.py | 4 ++-- estate/models/estate_property_offer.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b6890a97e9c..f2ab8f6366b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -57,7 +57,7 @@ class EstatePropertytModel(models.Model): copy=False, ) tag_ids = fields.Many2many( - 'estate.property.tag', + 'estate.property.tag', string='Tags', ) offer_ids = fields.One2many( @@ -105,7 +105,7 @@ def _check_expected_to_selling_price_ratio(self): limit = record.expected_price * 0.9 if float_compare(record.selling_price, limit, precision_digits=2) == -1: raise ValidationError('The selling price cannot be lower than 90% of the expected price!') - + _check_expected_price = models.Constraint( 'CHECK(expected_price > 0)', 'The Expected price of a property should be > 0', diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4914134aebe..60441f98239 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -83,7 +83,7 @@ def create(self, vals_list): prop.state = 'offer_received' return super().create(vals_list) - + _check_price = models.Constraint( 'CHECK(price > 0)', 'The offer price should be > 0', From 6d7c4d4bfd572c1a58209c4354f8c51eb923a295 Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Wed, 29 Apr 2026 15:05:35 +0200 Subject: [PATCH 35/36] [FIX] estate: handle null create_date in offer deadline compute --- estate/models/estate_property_offer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 60441f98239..d81ad890395 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -39,8 +39,9 @@ class EstatePropertyOffer(models.Model): @api.depends("validity", "create_date") def _compute_date_deadline(self): for record in self: + start_date = record.create_date.date() if record.create_date else fields.Date.today() record.date_deadline = fields.Date.add( - record.create_date.date() or fields.Date.today(), + start_date, days=record.validity ) From 02b6fb4aa2e416577fae06a90b4360b5c5cb453e Mon Sep 17 00:00:00 2001 From: "Ahmed Naem (Ahnae)" Date: Thu, 30 Apr 2026 13:54:23 +0200 Subject: [PATCH 36/36] [REF] awesome_owl: implement Owl framework tutorial exercises --- awesome_owl/static/src/card/card.js | 20 ++++++++++ awesome_owl/static/src/card/card.xml | 19 ++++++++++ awesome_owl/static/src/counter/counter.js | 19 ++++++++++ awesome_owl/static/src/counter/counter.xml | 11 ++++++ awesome_owl/static/src/playground.js | 15 +++++++- awesome_owl/static/src/playground.xml | 9 ++++- awesome_owl/static/src/todo/todoItem.js | 26 +++++++++++++ awesome_owl/static/src/todo/todoList.js | 39 ++++++++++++++++++++ awesome_owl/static/src/todo/todo_item.xml | 21 +++++++++++ awesome_owl/static/src/todo/todo_list.xml | 17 +++++++++ awesome_owl/static/src/utils/useAutofocus.js | 12 ++++++ 11 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml create mode 100644 awesome_owl/static/src/todo/todoItem.js create mode 100644 awesome_owl/static/src/todo/todoList.js create mode 100644 awesome_owl/static/src/todo/todo_item.xml create mode 100644 awesome_owl/static/src/todo/todo_list.xml create mode 100644 awesome_owl/static/src/utils/useAutofocus.js diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..fa424fe9fc3 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,20 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: String, + slots: { + type: Object, + optional: true, + }, + }; + + setup() { + this.state = useState({ isOpen: true }); + } + + toggle() { + this.state.isOpen = !this.state.isOpen; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..50a6df93d9f --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,19 @@ + + + +
+
+
+
+ +
+ +
+ +
+
+
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..88c50905228 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,19 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = { + onChange: {type: Function, optional: true}, + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + if (this.props.onChange) { + this.props.onChange(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..b2c2308ca74 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,11 @@ + + + +
+

Counter:

+ +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..bad284b0e18 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,18 @@ -import { Component } from "@odoo/owl"; +import { Component, useState, markup } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo/todoList"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Card, Counter, TodoList }; + + setup() { + this.htmlContent = markup("some content!"); + this.state = useState({ sum: 2 }); + } + + incrementSum = () => { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..7c1a8ed0e6d 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,15 @@ -
hello world
+ + + + + + Sum: +
-
diff --git a/awesome_owl/static/src/todo/todoItem.js b/awesome_owl/static/src/todo/todoItem.js new file mode 100644 index 00000000000..de58f099d12 --- /dev/null +++ b/awesome_owl/static/src/todo/todoItem.js @@ -0,0 +1,26 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todoItem"; + static props = { + todo: { + type: Object, + shape: { + id: Number, + description: String, + isCompleted: Boolean, + }, + }, + toggleTodo: Function, + removeTodo: Function + }; + + + onToggleState() { + this.props.toggleTodo(this.props.todo.id); + } + + onDelete(){ + this.props.removeTodo(this.props.todo.id) + } +} diff --git a/awesome_owl/static/src/todo/todoList.js b/awesome_owl/static/src/todo/todoList.js new file mode 100644 index 00000000000..0b4e1aba93d --- /dev/null +++ b/awesome_owl/static/src/todo/todoList.js @@ -0,0 +1,39 @@ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { TodoItem } from "./todoItem"; +import { useAutofocus } from "../utils/useAutofocus"; + +export class TodoList extends Component { + static template = "awesome_owl.todoList"; + static components = { TodoItem }; + + setup() { + this.nextId = 1 + this.todos = useState([]); + this.inputRef = useAutofocus('todo_input'); + } + + addTodo(ev) { + if (ev.keyCode === 13 && ev.target.value.trim() !== "") { + this.todos.push({ + id: this.nextId++, + description: ev.target.value, + isCompleted: false, + }); + ev.target.value = ""; + } + } + + toggleTodo = (todoId) => { + const todo = this.todos.find((t) => t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + removeTodo = (todoId) => { + const index = this.todos.findIndex((t) => t.id === todoId); + if (index >= 0) { + this.todos.splice(index, 1); + } + } +} diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..358d14e3d33 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,21 @@ + + + +
+ + + + +
+
+
diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..c99b58ac7e9 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,17 @@ + + + +
+

To-do List

+ + + + +
+
+
diff --git a/awesome_owl/static/src/utils/useAutofocus.js b/awesome_owl/static/src/utils/useAutofocus.js new file mode 100644 index 00000000000..04d49b25a00 --- /dev/null +++ b/awesome_owl/static/src/utils/useAutofocus.js @@ -0,0 +1,12 @@ +import { useRef, onMounted } from "@odoo/owl"; + + +export function useAutofocus(name) { + const ref = useRef(name); + onMounted(() => { + if (ref.el) { + ref.el.focus(); + } + }); + return ref; +}