From 537def18fdf7602c9b082acfeffe206db45f8885 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Tue, 18 Nov 2025 13:39:55 +0100 Subject: [PATCH 01/25] [ADD] estate: init commit for estate module --- 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..e77658138d1 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': 'Real Estate', + 'description': 'Real Estate !', + 'version': '1.0', + 'summary': 'Track Real Estate', + 'website': 'https://www.odoo.com/app/realestate', + 'author': 'Odoo S.A.', + 'license': 'LGPL-3', + 'depends': [ + 'base', + ] +} \ No newline at end of file From 7b4d00a85432c97a06e288a8f558f0d48595f481 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Tue, 18 Nov 2025 14:51:52 +0100 Subject: [PATCH 02/25] [IMP] estate: database field creation added more fields to the database table with needed requirements --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 36 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..9a5948f3180 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,34 @@ +from odoo import fields, models + +class Property(models.Model): + _name = "estate.property" + _description = "This is a estate property" + _order = "sequence" + + name = fields.Char('Name', required=True) + description = fields.Text('Description') + postcode = fields.Char('Postcode') + date_availability = fields.Date('Available From', copy=False) + + expected_price = fields.Float('Expected Price', required=True) + selling_price = fields.Float('Selling Price', readonly=True, copy=False) + + bedrooms = fields.Integer('Bedrooms', default=2) + living_area = fields.Integer('Living Area (sqm)') + facades = fields.Integer('Number of Facades') + + garage = fields.Boolean('Garage') + garden = fields.Boolean('Garden') + garden_area = fields.Integer('Garden Area (sqm)') + garden_orientation = fields.Selection( + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ], + string='Garden Orientation' + ) + + active = fields.Boolean('Active', default=True) + \ No newline at end of file From 19e2de2eae37f4464ae0360cd27f13ca2413cd53 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Tue, 18 Nov 2025 15:43:05 +0100 Subject: [PATCH 03/25] [IMP] estate: access rights added access rights --- estate/__manifest__.py | 6 +++++- estate/data/ir.model.access.csv | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 estate/data/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e77658138d1..384b841dc3d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,5 +8,9 @@ 'license': 'LGPL-3', 'depends': [ 'base', - ] + ], + 'data': [ + 'data/ir.model.access.csv' + ], + } \ No newline at end of file diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv new file mode 100644 index 00000000000..d9d6ba57cc5 --- /dev/null +++ b/estate/data/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 From 7fab1578946fd90d24eae5dd87709720adce0c37 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Wed, 19 Nov 2025 08:22:08 +0100 Subject: [PATCH 04/25] [IMP] estate: fixing linting issues fixed the issues on linting --- estate/__init__.py | 2 +- estate/__manifest__.py | 2 +- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 3 ++- 4 files changed, 5 insertions(+), 4 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 384b841dc3d..fc3949c23cb 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,4 +13,4 @@ 'data/ir.model.access.csv' ], -} \ 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 9a5948f3180..cbc0c2a01d8 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 = "This is a estate property" @@ -31,4 +32,4 @@ class Property(models.Model): ) active = fields.Boolean('Active', default=True) - \ No newline at end of file + From 6e5d530b69c7c9eaa50d1539dfbef480f2e5afae Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Wed, 19 Nov 2025 08:39:33 +0100 Subject: [PATCH 05/25] [IMP] estate: Creating ui action actions menu view --- estate/__manifest__.py | 3 ++- estate/view/__init__.py | 0 estate/view/estate_property_views.xml | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 estate/view/__init__.py create mode 100644 estate/view/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index fc3949c23cb..c0306574c5e 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,7 +10,8 @@ 'base', ], 'data': [ - 'data/ir.model.access.csv' + 'data/ir.model.access.csv', + 'view/estate_property_views.xml', ], } diff --git a/estate/view/__init__.py b/estate/view/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml new file mode 100644 index 00000000000..d63e5e7fcbc --- /dev/null +++ b/estate/view/estate_property_views.xml @@ -0,0 +1,18 @@ + + + Properties + estate.property + tree,form + + + + + + + + + \ No newline at end of file From ed34bfb174460596ae2bc595879b37cee0b2c323 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Wed, 19 Nov 2025 10:21:42 +0100 Subject: [PATCH 06/25] [IMP] estate: fix comments and apply suggestions --- estate/__manifest__.py | 5 +-- estate/models/estate_property.py | 31 ++++++++++++------- estate/{data => security}/ir.model.access.csv | 0 estate/view/__init__.py | 0 estate/view/estate_property_views.xml | 18 ----------- estate/views/estate_property_views.xml | 28 +++++++++++++++++ 6 files changed, 50 insertions(+), 32 deletions(-) rename estate/{data => security}/ir.model.access.csv (100%) delete mode 100644 estate/view/__init__.py delete mode 100644 estate/view/estate_property_views.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index c0306574c5e..e91e79c24aa 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,13 +5,14 @@ 'summary': 'Track Real Estate', 'website': 'https://www.odoo.com/app/realestate', 'author': 'Odoo S.A.', + 'application': True, 'license': 'LGPL-3', 'depends': [ 'base', ], 'data': [ - 'data/ir.model.access.csv', - 'view/estate_property_views.xml', + '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 cbc0c2a01d8..91b1a52956a 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,23 +1,31 @@ +from datetime import date, timedelta + +from dateutil.relativedelta import relativedelta + from odoo import fields, models -class Property(models.Model): +class EstateProperty(models.Model): _name = "estate.property" - _description = "This is a estate property" - _order = "sequence" + _description = "Estate property" + _order = "" - name = fields.Char('Name', required=True) + name = fields.Char('Title', required=True) description = fields.Text('Description') postcode = fields.Char('Postcode') - date_availability = fields.Date('Available From', copy=False) - + date_availability = fields.Date( + 'Available From', + copy=False, + default=lambda self: date.today() + relativedelta(months=3), + ) + expected_price = fields.Float('Expected Price', required=True) selling_price = fields.Float('Selling Price', readonly=True, copy=False) - - bedrooms = fields.Integer('Bedrooms', default=2) + + bedrooms = fields.Integer('Bedrooms', default=0) living_area = fields.Integer('Living Area (sqm)') - facades = fields.Integer('Number of Facades') - + facades = fields.Integer('Facades') + garage = fields.Boolean('Garage') garden = fields.Boolean('Garden') garden_area = fields.Integer('Garden Area (sqm)') @@ -28,8 +36,7 @@ class Property(models.Model): ('east', 'East'), ('west', 'West'), ], - string='Garden Orientation' + string='Garden Orientation', ) active = fields.Boolean('Active', default=True) - diff --git a/estate/data/ir.model.access.csv b/estate/security/ir.model.access.csv similarity index 100% rename from estate/data/ir.model.access.csv rename to estate/security/ir.model.access.csv diff --git a/estate/view/__init__.py b/estate/view/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml deleted file mode 100644 index d63e5e7fcbc..00000000000 --- a/estate/view/estate_property_views.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Properties - estate.property - tree,form - - - - - - - - - \ 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..36097c3f446 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,28 @@ + + + estate.property.list + estate.property + + + + + + + + + + + + Properties + estate.property + list,form + + + + + + + + + From 5d325ab9508fb3b449eef9d5081fda1e95022b95 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Wed, 19 Nov 2025 11:26:05 +0100 Subject: [PATCH 07/25] [IMP] estate: improvement to the state form added improvement to the properties --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 15 +++++++++++++-- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 8 -------- 4 files changed, 21 insertions(+), 11 deletions(-) create mode 100644 estate/views/estate_menus.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e91e79c24aa..a702ad935f5 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,6 +13,6 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], - } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 91b1a52956a..e2e5030025b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from datetime import date, timedelta +from datetime import date from dateutil.relativedelta import relativedelta @@ -22,7 +22,7 @@ class EstateProperty(models.Model): expected_price = fields.Float('Expected Price', required=True) selling_price = fields.Float('Selling Price', readonly=True, copy=False) - bedrooms = fields.Integer('Bedrooms', default=0) + bedrooms = fields.Integer('Bedrooms', default=2) living_area = fields.Integer('Living Area (sqm)') facades = fields.Integer('Facades') @@ -38,5 +38,16 @@ class EstateProperty(models.Model): ], string='Garden Orientation', ) + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer', 'Offer'), + ('received', 'Received'), + ('offer Accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + string='State',default='new',required=True,copy=False + ) active = fields.Boolean('Active', default=True) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..0876a7a26eb --- /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 index 36097c3f446..338a298ce13 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -17,12 +17,4 @@ estate.property list,form - - - - - - - From 86493036d4ad0957a1a7ca01ba21a25ca7cbcdd2 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Wed, 19 Nov 2025 11:39:09 +0100 Subject: [PATCH 08/25] [IMP] estate: fixing styling issues --- estate/models/estate_property.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e2e5030025b..03a6e2b6f40 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -47,7 +47,10 @@ class EstateProperty(models.Model): ('sold', 'Sold'), ('cancelled', 'Cancelled'), ], - string='State',default='new',required=True,copy=False + string='State', + default='new', + required=True, + copy=False, ) active = fields.Boolean('Active', default=True) From 76adf17eb206f2db4379bf2ca14835c201f2f04b Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Wed, 19 Nov 2025 15:48:39 +0100 Subject: [PATCH 09/25] [IMP] estate: adding features to the app added search / filter / form / header --- estate/__manifest__.py | 3 ++ estate/models/estate_property.py | 10 ++---- estate/views/estate_property_views.xml | 8 ----- estate/views/estate_tree.xml | 17 +++++++++ estate/views/estate_view_form.xml | 50 ++++++++++++++++++++++++++ estate/views/estate_view_search.xml | 28 +++++++++++++++ 6 files changed, 100 insertions(+), 16 deletions(-) create mode 100644 estate/views/estate_tree.xml create mode 100644 estate/views/estate_view_form.xml create mode 100644 estate/views/estate_view_search.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a702ad935f5..950f6e43d77 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,6 +13,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_tree.xml', 'views/estate_menus.xml', + 'views/estate_view_form.xml', + 'views/estate_view_search.xml', ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 03a6e2b6f40..cefd8fb505d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,7 +1,5 @@ from datetime import date - from dateutil.relativedelta import relativedelta - from odoo import fields, models @@ -12,20 +10,17 @@ class EstateProperty(models.Model): name = fields.Char('Title', required=True) description = fields.Text('Description') - postcode = fields.Char('Postcode') + post_code = fields.Char('Postcode') date_availability = fields.Date( 'Available From', copy=False, default=lambda self: date.today() + relativedelta(months=3), ) - expected_price = fields.Float('Expected Price', required=True) selling_price = fields.Float('Selling Price', readonly=True, copy=False) - bedrooms = fields.Integer('Bedrooms', default=2) living_area = fields.Integer('Living Area (sqm)') facades = fields.Integer('Facades') - garage = fields.Boolean('Garage') garden = fields.Boolean('Garden') garden_area = fields.Integer('Garden Area (sqm)') @@ -43,7 +38,7 @@ class EstateProperty(models.Model): ('new', 'New'), ('offer', 'Offer'), ('received', 'Received'), - ('offer Accepted', 'Offer Accepted'), + ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled'), ], @@ -52,5 +47,4 @@ class EstateProperty(models.Model): required=True, copy=False, ) - active = fields.Boolean('Active', default=True) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 338a298ce13..3faad4288ab 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,14 +2,6 @@ estate.property.list estate.property - - - - - - - - diff --git a/estate/views/estate_tree.xml b/estate/views/estate_tree.xml new file mode 100644 index 00000000000..89106a95c17 --- /dev/null +++ b/estate/views/estate_tree.xml @@ -0,0 +1,17 @@ + + + Estate Property Tree + estate.property + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_view_form.xml b/estate/views/estate_view_form.xml new file mode 100644 index 00000000000..0c50d7cc400 --- /dev/null +++ b/estate/views/estate_view_form.xml @@ -0,0 +1,50 @@ + + + estate.property.form + estate.property + +
+
+ +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
\ No newline at end of file diff --git a/estate/views/estate_view_search.xml b/estate/views/estate_view_search.xml new file mode 100644 index 00000000000..5b75837daf2 --- /dev/null +++ b/estate/views/estate_view_search.xml @@ -0,0 +1,28 @@ + + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + + + From 299f69d8ea08db2be2e4740ce6118be2ea1f36b5 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Thu, 20 Nov 2025 11:27:10 +0100 Subject: [PATCH 10/25] [FIX] estate: fix for ci checks fixed the issue for the styles and the tutorial syntax --- estate/models/estate_property.py | 2 +- estate/views/estate_property_views.xml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index cefd8fb505d..15c5f0c607c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -38,7 +38,7 @@ class EstateProperty(models.Model): ('new', 'New'), ('offer', 'Offer'), ('received', 'Received'), - ('offer_accepted', 'Offer Accepted'), + ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled'), ], diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3faad4288ab..bc1922fc19c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,6 +2,11 @@ estate.property.list estate.property + + + + + From fac371f5d0a6f8648d494cb89c1eea8c1052ab85 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Fri, 21 Nov 2025 11:13:39 +0100 Subject: [PATCH 11/25] [IMP] estate: Adding features to the estate app added tags types.. and accesses and infos in the form --- .gitignore | 3 ++ estate/__manifest__.py | 21 +++++++------ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 9 +++++- estate/models/estate_property_offer.py | 17 ++++++++++ estate/models/estate_property_tag.py | 9 ++++++ estate/models/estate_property_type.py | 14 +++++++++ estate/security/ir.model.access.csv | 7 +++-- .../{estate_tree.xml => estate_list.xml} | 1 + estate/views/estate_menus.xml | 8 +++-- estate/views/estate_property_form_views.xml | 26 ++++++++++++++++ estate/views/estate_property_offer_views.xml | 27 ++++++++++++++++ estate/views/estate_property_tag_views.xml | 18 +++++++++++ estate/views/estate_property_type_views.xml | 31 +++++++++++++++++++ estate/views/estate_property_views.xml | 1 + estate/views/estate_view_form.xml | 15 +++++++++ estate/views/estate_view_search.xml | 1 + 17 files changed, 196 insertions(+), 15 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 rename estate/views/{estate_tree.xml => estate_list.xml} (90%) create mode 100644 estate/views/estate_property_form_views.xml 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/.gitignore b/.gitignore index b6e47617de1..93a80466f11 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,9 @@ MANIFEST *.manifest *.spec +#editor +.vscode/ + # Installer logs pip-log.txt pip-delete-this-directory.txt diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 950f6e43d77..c35d38c0064 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,15 +7,16 @@ 'author': 'Odoo S.A.', 'application': True, 'license': 'LGPL-3', - 'depends': [ - 'base', + 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_property_views.xml', + 'views/estate_list.xml', + 'views/estate_view_form.xml', + 'views/estate_view_search.xml', + 'views/estate_menus.xml', ], - 'data': [ - 'security/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_tree.xml', - 'views/estate_menus.xml', - 'views/estate_view_form.xml', - 'views/estate_view_search.xml', - ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 15c5f0c607c..7fefb90f475 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,7 +8,7 @@ class EstateProperty(models.Model): _description = "Estate property" _order = "" - name = fields.Char('Title', required=True) + name = fields.Text('Title', required=True) description = fields.Text('Description') post_code = fields.Char('Postcode') date_availability = fields.Date( @@ -48,3 +48,10 @@ class EstateProperty(models.Model): copy=False, ) active = fields.Boolean('Active', default=True) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + user_id = fields.Many2one( + 'res.users', string='Salesman', default=lambda self: self.env.user + ) + buyer_id = fields.Many2one("res.partner", string="Buyer", readonly=True, 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..0b14132add1 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offers" + _order = "price" + + price = fields.Float("Price", required=True) + state = fields.Selection( + [('accepted', 'Accepted'), ('refused', 'Refused')], + string="Status", + default=False, + 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..6edd4d45013 --- /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" + _order = "name" + + name = fields.Char("Name", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..12ec06fad19 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,14 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + _order = "sequence, name" + + name = fields.Char("Name", required=True) + sequence = fields.Integer("Sequence") + property_ids = fields.One2many( + "estate.property", "property_type_id", string="Properties" + ) + offer_count = fields.Integer(string="Offers count", compute="_compute_offer") diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d9d6ba57cc5..bdc8b769cdd 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 +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 +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 \ No newline at end of file diff --git a/estate/views/estate_tree.xml b/estate/views/estate_list.xml similarity index 90% rename from estate/views/estate_tree.xml rename to estate/views/estate_list.xml index 89106a95c17..7ccc766e0e5 100644 --- a/estate/views/estate_tree.xml +++ b/estate/views/estate_list.xml @@ -5,6 +5,7 @@ + diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 0876a7a26eb..518b45027d7 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,11 @@ - + + + + + - + \ No newline at end of file diff --git a/estate/views/estate_property_form_views.xml b/estate/views/estate_property_form_views.xml new file mode 100644 index 00000000000..058391929a0 --- /dev/null +++ b/estate/views/estate_property_form_views.xml @@ -0,0 +1,26 @@ + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + + + + +
+
+
+ + + Property Offers + estate.property.offer + tree,form + +
\ 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..6c115f8329b --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,27 @@ + + + estate.property.offer.form + estate.property.offer + +
+ + + + + +
+
+
+ + + estate.property.offer.tree + 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..ceaea50ef75 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,18 @@ + + + estate.property.tag.tree + estate.property.tag + + + + + + + + + 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..943e10fa83a --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,31 @@ + + + estate.property.type.list + estate.property.type + + + + + + + + + estate.property.type.form + estate.property.type + +
+ + + + + +
+
+
+ + + Property Types + estate.property.type + list,form + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index bc1922fc19c..a6426e2cd27 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,6 +5,7 @@ +
diff --git a/estate/views/estate_view_form.xml b/estate/views/estate_view_form.xml index 0c50d7cc400..99fb9cd4d1f 100644 --- a/estate/views/estate_view_form.xml +++ b/estate/views/estate_view_form.xml @@ -13,6 +13,10 @@ + + + + @@ -42,6 +46,17 @@ + + + + + + + + + + + diff --git a/estate/views/estate_view_search.xml b/estate/views/estate_view_search.xml index 5b75837daf2..12ddf993e1e 100644 --- a/estate/views/estate_view_search.xml +++ b/estate/views/estate_view_search.xml @@ -16,6 +16,7 @@ + From 6b9945dc5f0a8900c1c047b25109de65e2221d50 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Fri, 21 Nov 2025 11:48:44 +0100 Subject: [PATCH 12/25] [IMP] estate: added feature to compute fields Chapter 8: Computed Fields And Onchanges count the oriontation depend of criteria using action --- estate/models/estate_property.py | 32 +++++++++++++++++++- estate/models/estate_property_offer.py | 17 ++++++++++- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_view_form.xml | 2 ++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7fefb90f475..344d5accdf6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,6 @@ from datetime import date from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -55,3 +55,33 @@ class EstateProperty(models.Model): buyer_id = fields.Many2one("res.partner", string="Buyer", readonly=True, copy=False) tag_ids = fields.Many2many("estate.property.tag", string="Tags") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + total_area = fields.Integer("Total Area (sqm)", compute="_compute_total_area") + best_price = fields.Float( + "Best Offer", + compute="_compute_best_price", readonly=True + ) + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for rec in self: + rec.total_area = (rec.living_area or 0) + (rec.garden_area or 0) + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for rec in self: + prices = rec.offer_ids.mapped('price') + rec.best_price = max(prices) if prices else 0.0 + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for prop in self: + prop.total_area = prop.living_area + prop.garden_area + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 0b14132add1..4027994c4ab 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): @@ -15,3 +15,18 @@ 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( + "Validity (days)", default=7 + ) + date_deadline = fields.Date( + "Deadline", compute="_compute_date_deadline", store=True + ) + @api.depends('validity') + def _compute_date_deadline(self): + for offer in self: + if offer._origin.validity: + offer.date_deadline = fields.Date.add( + offer.create_date.date(), days=offer.validity + ) + else: + offer.date_deadline = False \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 6c115f8329b..949e3df207d 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -21,6 +21,8 @@ + +
diff --git a/estate/views/estate_view_form.xml b/estate/views/estate_view_form.xml index 99fb9cd4d1f..e511632e936 100644 --- a/estate/views/estate_view_form.xml +++ b/estate/views/estate_view_form.xml @@ -25,6 +25,7 @@ + @@ -43,6 +44,7 @@ + From 063a6df700927fa5a438307c7969b7ebfbc594cf Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Fri, 21 Nov 2025 12:51:55 +0100 Subject: [PATCH 13/25] [FIX] estate: fixed style issues --- 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 4027994c4ab..113785b5e6b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -21,6 +21,7 @@ class EstatePropertyOffer(models.Model): date_deadline = fields.Date( "Deadline", compute="_compute_date_deadline", store=True ) + @api.depends('validity') def _compute_date_deadline(self): for offer in self: @@ -29,4 +30,4 @@ def _compute_date_deadline(self): offer.create_date.date(), days=offer.validity ) else: - offer.date_deadline = False \ No newline at end of file + offer.date_deadline = False From 6541bd94003b7d06e95f0ab8edebcb2bc1f7a4cf Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Fri, 21 Nov 2025 13:57:05 +0100 Subject: [PATCH 14/25] [IMP] estate: added accept/refuse offer added requirement of chapter 9 --- estate/models/estate_property.py | 14 +++++++++++-- estate/models/estate_property_offer.py | 21 +++++++++++++++++--- estate/views/estate_property_offer_views.xml | 6 +++++- estate/views/estate_view_form.xml | 6 ++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 344d5accdf6..3aa5bd71f62 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from datetime import date from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError, ValidationError from odoo import api, fields, models @@ -57,8 +58,7 @@ class EstateProperty(models.Model): offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") total_area = fields.Integer("Total Area (sqm)", compute="_compute_total_area") best_price = fields.Float( - "Best Offer", - compute="_compute_best_price", readonly=True + "Best Offer", compute="_compute_best_price", readonly=True ) @api.depends('living_area', 'garden_area') @@ -85,3 +85,13 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + def action_sold(self): + if "cancelled" in self.mapped("state"): + raise UserError("Canceled property cannot be sold !") + return self.write({"state": "sold"}) + + def action_cancel(self): + if "sold" in self.mapped("state"): + raise UserError("Sold property cannot be canceled !") + return self.write({"state": "cancelled"}) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 113785b5e6b..5415a54e429 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -15,9 +15,7 @@ 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( - "Validity (days)", default=7 - ) + validity = fields.Integer("Validity (days)", default=7) date_deadline = fields.Date( "Deadline", compute="_compute_date_deadline", store=True ) @@ -31,3 +29,20 @@ def _compute_date_deadline(self): ) else: offer.date_deadline = False + + def action_accept(self): + for offer in self: + offer.state = 'accepted' + offer.property_id.selling_price = offer.price + offer.property_id.buyer_id = offer.partner_id.id + offer.property_id.state = 'offer_accepted' + other_offers = offer.property_id.offer_ids.filtered( + lambda o: o.id != offer.id and o.state != 'refused' + ) + other_offers.state = 'refused' + return True + + def action_refuse(self): + for offer in self: + offer.state = 'refused' + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 949e3df207d..ca44687f9fa 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -12,7 +12,7 @@ - + estate.property.offer.tree estate.property.offer @@ -23,6 +23,10 @@ + + + + + + + + + + + + + + + + + + + + + + + Estate Property Type + estate.property.type + list,form + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a6426e2cd27..a6cf65cc9df 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,9 +3,11 @@ estate.property.list estate.property - + + + @@ -14,5 +16,6 @@ Properties estate.property list,form + {'search_default_is_new': 1} diff --git a/estate/views/estate_view_form.xml b/estate/views/estate_view_form.xml index bab9b96c5d3..e151a13fe5d 100644 --- a/estate/views/estate_view_form.xml +++ b/estate/views/estate_view_form.xml @@ -6,12 +6,10 @@
-
@@ -20,8 +18,8 @@
- - + + @@ -48,14 +46,14 @@ - - + + - + @@ -70,4 +68,4 @@
- \ No newline at end of file + diff --git a/estate/views/estate_view_search.xml b/estate/views/estate_view_search.xml index 12ddf993e1e..ba23f71bd53 100644 --- a/estate/views/estate_view_search.xml +++ b/estate/views/estate_view_search.xml @@ -21,8 +21,8 @@ - - + + From 5012b1c0596c670aaeefd0936561bfb876b3782d Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Mon, 24 Nov 2025 11:38:42 +0100 Subject: [PATCH 17/25] [IMP] estate: Chapter 12: Inheritance --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 20 ++++++------------ estate/models/estate_property_offer.py | 27 +++++++++++++++++++++++++ estate/models/res_users.py | 9 +++++++++ estate/views/estate_res_users_views.xml | 22 ++++++++++++++++++++ 6 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/estate_res_users_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ea8da077959..e72ace48d35 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -18,6 +18,7 @@ 'views/estate_list.xml', 'views/estate_view_form.xml', 'views/estate_view_search.xml', + 'views/estate_res_users_views.xml', 'views/estate_menus.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 07cd1028c4a..45b9b962182 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -68,20 +68,6 @@ class EstateProperty(models.Model): "Best Offer", compute="_compute_best_price", readonly=True ) - # 💡 REPLACED deprecated models.Constraint with _sql_constraints - _sql_constraints = [ - ( - 'check_expected_price', - 'CHECK(expected_price > 0)', - 'The expected price must be strictly positive.', - ), - ( - 'check_selling_price', - 'CHECK(selling_price >= 0)', - 'The selling price must be positive or zero.', - ), - ] - @api.depends('living_area', 'garden_area') def _compute_total_area(self): for rec in self: @@ -135,3 +121,9 @@ def _check_selling_price_constraint(self): _check_selling_price = models.Constraint( 'CHECK(selling_price >= 0)', 'The selling price must be positive or zero.' ) + + @api.ondelete(at_uninstall=False) + def _ondelete_check_state(self): + for prop in self: + if prop.state not in ('new', 'cancelled'): + raise UserError('Only properties in New or Cancelled state can be deleted.') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 87b5275815f..93e94cddaeb 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ from odoo import api, fields, models +from odoo.exceptions import ValidationError +from odoo.tools.float_utils import float_compare from datetime import timedelta @@ -57,3 +59,28 @@ def action_refuse(self): for offer in self: offer.state = 'refused' return True + + @api.model + def create(self, vals_list): + if isinstance(vals_list, dict): + vals_list = [vals_list] + + for vals in vals_list: + prop_id = vals.get('property_id') + price = vals.get('price') + if prop_id and price is not None: + prop = self.env['estate.property'].browse(prop_id) + existing_prices = prop.offer_ids.mapped('price') + if existing_prices: + best = max(existing_prices) + if float_compare(price, best, precision_digits=2) < 0: + raise ValidationError( + 'You cannot create an offer lower than an existing offer.' + ) + + offers = super(EstatePropertyOffer, self).create(vals_list) + + for offer in offers: + if offer.property_id: + offer.property_id.state = 'received' + return offers diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..1e25beb204f --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + "estate.property", "user_id", string="Properties", domain="[('state', '=', 'new')]" + ) diff --git a/estate/views/estate_res_users_views.xml b/estate/views/estate_res_users_views.xml new file mode 100644 index 00000000000..740a5b032e8 --- /dev/null +++ b/estate/views/estate_res_users_views.xml @@ -0,0 +1,22 @@ + + + + res.users.form.inherit.estate.properties + res.users + + + + + + + + + + + + + + + + + From 0050223f4565db85f6b1231d0fc3cb01e0a6da08 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Mon, 24 Nov 2025 11:50:35 +0100 Subject: [PATCH 18/25] [FIX] estate: fixed warnings fixed label warning and constraint --- estate/models/estate_property_type.py | 7 ++++--- estate/models/res_users.py | 2 +- estate/views/estate_res_users_views.xml | 10 ++-------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index cb957fbbc35..9d0ed62139f 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -16,9 +16,10 @@ class EstatePropertyType(models.Model): "estate.property.offer", "property_type_id", string="Offers" ) - _sql_constraints = [ - ('unique_type_name', 'UNIQUE(name)', 'The property type name must be unique.') - ] + # SQL constraints declared using the new API + _unique_type_name = models.Constraint( + 'UNIQUE(name)', 'The property type name must be unique.' + ) @api.depends('offer_ids') def _compute_offer(self): diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 1e25beb204f..df68c55fdd2 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -5,5 +5,5 @@ class ResUsers(models.Model): _inherit = "res.users" property_ids = fields.One2many( - "estate.property", "user_id", string="Properties", domain="[('state', '=', 'new')]" + "estate.property", "user_id", string="Estate Properties", domain="[('state', '=', 'new')]" ) diff --git a/estate/views/estate_res_users_views.xml b/estate/views/estate_res_users_views.xml index 740a5b032e8..7657bc519d0 100644 --- a/estate/views/estate_res_users_views.xml +++ b/estate/views/estate_res_users_views.xml @@ -6,14 +6,8 @@ - - - - - - - - + + From d9de3d1939c92fa9ab94e24d1ed284dd13eae064 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Mon, 24 Nov 2025 13:55:52 +0100 Subject: [PATCH 19/25] [IMP] estate: added invoicing added invoicing debuged an issue when invoices does not appear --- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 13 ++++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 39 ++++++++++++++++++++++++ 4 files changed, 54 insertions(+) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..ad8df6d92b2 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,13 @@ +{ + 'name': 'Estate - Accounting Link', + 'summary': 'Link between Real Estate and Accounting', + 'version': '1.0', + 'category': 'Accounting/Localisation', + 'author': 'Odoo S.A.', + 'license': 'LGPL-3', + 'depends': ['estate', 'account'], + 'data': [ + ], + 'installable': True, + 'application': False, +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..40854db05e3 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,39 @@ +from odoo import Command, models + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_sold(self): + res = super(EstateProperty, self).action_sold() + + AccountMove = self.env['account.move'] + Journal = self.env['account.journal'] + for prop in self: + partner = prop.buyer_id + if not partner: + continue + journal = Journal.search([('type', '=', 'sale')], limit=1) + commission_amount = 0.0 + if prop.selling_price: + commission_amount = round(0.06 * float(prop.selling_price), 2) + vals = { + 'partner_id': partner.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create({ + 'name': 'Commission (6%)', + 'quantity': 1.0, + 'price_unit': commission_amount, + }), + Command.create({ + 'name': 'Administrative fees', + 'quantity': 1.0, + 'price_unit': 100.00, + }), + ], + } + if journal: + vals['journal_id'] = journal.id + AccountMove.create(vals) + return res From 7698893d800eec6ec73a7afd13bec5c1ba05f9dd Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Mon, 24 Nov 2025 15:02:48 +0100 Subject: [PATCH 20/25] [IMP] estate: adding Kanban ViewKanban added kanban view and disabled DND, grouped the view --- estate/views/estate_property_views.xml | 31 +++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a6cf65cc9df..9f8400bf90e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -12,10 +12,39 @@ + + estate.property.kanban + estate.property + + + + + +
+ +
+ Expected Price: +
+
+ Best Offer: +
+
+ Selling Price: +
+
+ +
+
+
+
+
+
+
+ Properties estate.property - list,form + list,form,kanban {'search_default_is_new': 1} From 5143ed608babb638adefec11eb1aaa126328a163 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Mon, 24 Nov 2025 15:38:46 +0100 Subject: [PATCH 21/25] [FIX] estate: fixed spaces for linting --- estate/models/estate_property.py | 4 +++- estate/models/res_users.py | 5 ++++- estate_account/models/estate_property.py | 24 ++++++++++++++---------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 45b9b962182..1f8e7a02aa4 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -126,4 +126,6 @@ def _check_selling_price_constraint(self): def _ondelete_check_state(self): for prop in self: if prop.state not in ('new', 'cancelled'): - raise UserError('Only properties in New or Cancelled state can be deleted.') + raise UserError( + 'Only properties in New or Cancelled state can be deleted.' + ) diff --git a/estate/models/res_users.py b/estate/models/res_users.py index df68c55fdd2..3e2001f1432 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -5,5 +5,8 @@ class ResUsers(models.Model): _inherit = "res.users" property_ids = fields.One2many( - "estate.property", "user_id", string="Estate Properties", domain="[('state', '=', 'new')]" + "estate.property", + "user_id", + string="Estate Properties", + domain="[('state', '=', 'new')]", ) diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 40854db05e3..915c6024ef7 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -21,16 +21,20 @@ def action_sold(self): 'partner_id': partner.id, 'move_type': 'out_invoice', 'invoice_line_ids': [ - Command.create({ - 'name': 'Commission (6%)', - 'quantity': 1.0, - 'price_unit': commission_amount, - }), - Command.create({ - 'name': 'Administrative fees', - 'quantity': 1.0, - 'price_unit': 100.00, - }), + Command.create( + { + 'name': 'Commission (6%)', + 'quantity': 1.0, + 'price_unit': commission_amount, + } + ), + Command.create( + { + 'name': 'Administrative fees', + 'quantity': 1.0, + 'price_unit': 100.00, + } + ), ], } if journal: From 95caa6b8b463b70ee1bbd22cc11407aef7df3e7f Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Tue, 25 Nov 2025 08:11:56 +0100 Subject: [PATCH 22/25] [FIX] estate: fixing lint and style issues --- estate/models/estate_property.py | 2 +- estate/models/estate_property_offer.py | 2 +- estate_account/models/estate_property.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1f8e7a02aa4..d4e7d8d167e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -89,7 +89,7 @@ def _onchange_garden(self): self.garden_orientation = False def action_sold(self): - if any([prop.state == "cancelled" for prop in self]): + if any(prop.state == "cancelled" for prop in self): raise UserError("Canceled property cannot be sold !") self.state = 'sold' return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 93e94cddaeb..2bb65f21e58 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -78,7 +78,7 @@ def create(self, vals_list): 'You cannot create an offer lower than an existing offer.' ) - offers = super(EstatePropertyOffer, self).create(vals_list) + offers = super().create(vals_list) for offer in offers: if offer.property_id: diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 915c6024ef7..7d4bb8c44a1 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -5,7 +5,7 @@ class EstateProperty(models.Model): _inherit = "estate.property" def action_sold(self): - res = super(EstateProperty, self).action_sold() + res = super().action_sold() AccountMove = self.env['account.move'] Journal = self.env['account.journal'] From da766e18b705c133b14304aa8c65c618cc748899 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Tue, 25 Nov 2025 09:43:22 +0100 Subject: [PATCH 23/25] [FIX] estate: fixed the field hidden --- estate/views/estate_property_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9f8400bf90e..4f39fb15768 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -7,7 +7,7 @@ - + From cffbf017e8fd5e6cdede482c92b4dbaa0430abe9 Mon Sep 17 00:00:00 2001 From: "youness benbraitit (yoben)" Date: Tue, 25 Nov 2025 10:01:38 +0100 Subject: [PATCH 24/25] [FIX] estate: fixing code guidance issue of field --- estate/views/estate_property_offer_views.xml | 2 +- estate/views/estate_property_type_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 79b7077ec25..8b2bd8723fa 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -34,7 +34,7 @@ - + + + + +
+
+ + + + + + +
+
+ + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.js b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.js new file mode 100644 index 00000000000..cdf371543b5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.js @@ -0,0 +1,35 @@ +import { Component } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { registry } from "@web/core/registry"; + +export class DashboardConfigDialog extends Component { + static template = "awesome_dashboard.DashboardConfigDialog"; + static components = { Dialog }; + static props = { + close: Function, + onApply: Function, + currentConfig: Array, + }; + + setup() { + this.allItems = registry.category("awesome_dashboard").getAll(); + this.hiddenItems = new Set(this.props.currentConfig); + } + + toggleItem = (itemId) => { + if (this.hiddenItems.has(itemId)) { + this.hiddenItems.delete(itemId); + } else { + this.hiddenItems.add(itemId); + } + } + + isItemVisible = (itemId) => { + return !this.hiddenItems.has(itemId); + } + + apply = () => { + this.props.onApply(Array.from(this.hiddenItems)); + this.props.close(); + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.xml b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.xml new file mode 100644 index 00000000000..82ad8ec810c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_config_dialog.xml @@ -0,0 +1,30 @@ + + + + + +
+
Select items to display:
+ +
+ + +
+
+
+ + + + +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js new file mode 100644 index 00000000000..92dda619bce --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.js @@ -0,0 +1,14 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { type: Number, optional: true }, + slots: { type: Object, optional: true }, + }; + + get width() { + const size = this.props.size || 1; + return `${18 * size}rem`; + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item.xml new file mode 100644 index 00000000000..b41fe3561fb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item.xml @@ -0,0 +1,10 @@ + + + + +
+ +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..f251186e352 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,71 @@ +import { registry } from "@web/core/registry"; +import { NumberCard } from "./number_card"; +import { PieChartCard } from "./pie_chart_card"; + +const dashboardRegistry = registry.category("awesome_dashboard"); + +dashboardRegistry.add("nb_new_orders", { + id: "nb_new_orders", + description: "New Orders", + Component: NumberCard, + props: (data) => ({ + title: "New Orders", + value: data.nb_new_orders, + color: "primary", + }), +}); + +dashboardRegistry.add("total_amount", { + id: "total_amount", + description: "Total Amount", + Component: NumberCard, + props: (data) => ({ + title: "Total Amount", + value: data.total_amount, + color: "success", + }), +}); + +dashboardRegistry.add("average_quantity", { + id: "average_quantity", + description: "Average Quantity", + Component: NumberCard, + props: (data) => ({ + title: "Average Quantity", + value: data.average_quantity, + color: "info", + }), +}); + +dashboardRegistry.add("nb_cancelled_orders", { + id: "nb_cancelled_orders", + description: "Cancelled Orders", + Component: NumberCard, + props: (data) => ({ + title: "Cancelled Orders", + value: data.nb_cancelled_orders, + color: "danger", + }), +}); + +dashboardRegistry.add("average_time", { + id: "average_time", + description: "Average Time", + Component: NumberCard, + props: (data) => ({ + title: "Avg Time (hours)", + value: data.average_time, + color: "warning", + }), +}); + +dashboardRegistry.add("orders_by_size", { + id: "orders_by_size", + description: "T-Shirt Sizes", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "T-Shirt Sizes", + data: data.orders_by_size, + }), +}); diff --git a/awesome_dashboard/static/src/dashboard/number_card.js b/awesome_dashboard/static/src/dashboard/number_card.js new file mode 100644 index 00000000000..c7e6b82dc45 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card.js @@ -0,0 +1,10 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: String, + value: [Number, String], + color: { type: String, optional: true }, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card.xml new file mode 100644 index 00000000000..13ea271bf52 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card.xml @@ -0,0 +1,13 @@ + + + + +
+
+ +
+
+
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart.js new file mode 100644 index 00000000000..0a1959da684 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart.js @@ -0,0 +1,46 @@ +import { Component, onWillStart, useRef, onMounted } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + data: Object, + }; + + setup() { + this.canvasRef = useRef("canvas"); + + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js"); + }); + + onMounted(() => { + this.renderChart(); + }); + } + + renderChart() { + const ctx = this.canvasRef.el.getContext("2d"); + new Chart(ctx, { + type: "pie", + data: { + labels: Object.keys(this.props.data), + datasets: [{ + label: "T-Shirt Sizes", + data: Object.values(this.props.data), + backgroundColor: [ + "#FF6384", + "#36A2EB", + "#FFCE56", + "#4BC0C0", + "#9966FF", + ], + }], + }, + options: { + responsive: true, + maintainAspectRatio: false, + }, + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart.xml new file mode 100644 index 00000000000..92d4ac3420d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart.xml @@ -0,0 +1,10 @@ + + + + +
+ +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card.js new file mode 100644 index 00000000000..2b3f270d4ca --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card.js @@ -0,0 +1,11 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "./pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { PieChart }; + static props = { + title: String, + data: Object, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card.xml new file mode 100644 index 00000000000..b3b3aba0692 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card.xml @@ -0,0 +1,11 @@ + + + + +
+
+
+ +
+ +
diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js new file mode 100644 index 00000000000..07c35e1ab0c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,24 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + +export const statisticsService = { + start() { + const statistics = reactive({}); + + async function loadStatistics() { + const data = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, data); + } + + loadStatistics(); + + setInterval(loadStatistics, 10000); + + return { + statistics, + }; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..732cf365add --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,12 @@ +import { Component, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + +class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..5c49c0e5146 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,17 @@ +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 }); + } + + toggleContent() { + 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..d3d1a7a7ea0 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,18 @@ + + + +
+
+
+
+ +
+
+ +
+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..6fff049f70a --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,20 @@ +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.count = useState({ value: 0 }); + } + + increment() { + this.count.value++; + if (this.props.onChange) { + this.props.onChange(this.count.value); + } + } +} + diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..2dbd6f6a612 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + +
+
Counter:
+ +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..ecbb47be0bf 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,17 @@ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo_list/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + setup() { + 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..6813c70787e 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,34 @@ -
- hello world +
+ +

This is simple text content

+
+ + + + +

This card has bold and italic text!

+
+
+
+

Counters

+
+
+ +
+
+ +
+
+ +
+
+
Sum:
+
+
-
diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js new file mode 100644 index 00000000000..bd3568eeeba --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,17 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + static props = { + todo: { + type: Object, + shape: { + id: Number, + description: String, + isCompleted: Boolean, + }, + }, + toggleState: Function, + removeTodo: Function, + }; +} diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml new file mode 100644 index 00000000000..a8b80483011 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,10 @@ + + + +
+ + . + +
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..9b4fdcef39c --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,43 @@ +import { Component, useState } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutofocus } from "../utils"; + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + + setup() { + this.todos = useState([ + { id: 1, description: "buy milk", isCompleted: true }, + { id: 2, description: "clean room", isCompleted: false }, + { id: 3, description: "go to the gym", isCompleted: false }, + ]); + this.inputRef = useAutofocus("add-input"); + } + + toggleState = (todoId) => { + const todo = this.todos.find(t => t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + addTodo(ev) { + if (ev.keyCode === 13 && ev.target.value.trim()) { + const newId = this.todos.length > 0 ? Math.max(...this.todos.map(t => t.id)) + 1 : 1; + this.todos.push({ + id: newId, + description: ev.target.value.trim(), + isCompleted: false, + }); + ev.target.value = ""; + } + } + + 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_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..03af5df2f78 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,14 @@ + + + +
+

Todo List

+ +
+ + + +
+
+
+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..3e27f89e7dd --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,11 @@ +import { useRef, onMounted } from "@odoo/owl"; + +export function useAutofocus(refName = "autofocus") { + const ref = useRef(refName); + onMounted(() => { + if (ref.el) { + ref.el.focus(); + } + }); + return ref; +} diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e72ace48d35..ba5bd844356 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,7 +7,7 @@ 'author': 'Odoo S.A.', 'application': True, 'license': 'LGPL-3', - 'depends': ['base'], + 'depends': ['base', 'awesome_owl', 'awesome_dashboard'], 'data': [ 'security/ir.model.access.csv', 'views/estate_property_tag_views.xml',