From 3c4ec5cc346af4a464b3715bc6d9429bc2ef7270 Mon Sep 17 00:00:00 2001 From: dalio Date: Tue, 21 Apr 2026 13:55:15 +0200 Subject: [PATCH 01/28] [REF] Internal: test setup dalio --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a0158d919ee..4a653a5e569 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,4 @@ tutorial's solutions, and one for the [Master the Odoo web framework](https://www.odoo.com/documentation/latest/developer/tutorials/master_odoo_web_framework.html) tutorial's solutions. For example, `17.0`, `17.0-discover-js-framework-solutions` and `17.0-master-odoo-web-framework-solutions`. +# Test setup dalio From 46f010ccf20a9d359b700c006525699cf9e72aa7 Mon Sep 17 00:00:00 2001 From: dalio Date: Tue, 21 Apr 2026 17:23:25 +0200 Subject: [PATCH 02/28] [ADD] estate: add model --- .vscode/settings.json | 8 ++++++++ estate/__init__.py | 0 estate/__manifest__.py | 7 +++++++ estate/models/__init__.py | 0 estate/models/estate_property.py | 4 ++++ 5 files changed, 19 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000000..535bfc54d98 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "python.analysis.extraPaths": [ + "/home/odoo/src/venv", + "/home/odoo/src/odoo", + "/home/odoo/src/enterprise" + ], + "python.defaultInterpreterPath": "/home/odoo/src/venv/bin/python3" +} \ No newline at end of file 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..e63b35840dd --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,7 @@ +{ + 'name': 'Real Estate', + 'depends': [ + 'base_setup' + ], + 'application': True +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..9d7c41260c6 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,4 @@ +from odoo import models + +class TestModel(models.Model): + _name = "test_model" From 2a943c1a9d2abe4e82ca1f2be9b84ea7701e1f7b Mon Sep 17 00:00:00 2001 From: dalio Date: Wed, 22 Apr 2026 09:47:17 +0200 Subject: [PATCH 03/28] [ADD] estate: add estate_property --- .vscode/settings.json | 2 +- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 31 ++++++++++++++++++++++++++++--- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 535bfc54d98..996e8684419 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,5 @@ "/home/odoo/src/odoo", "/home/odoo/src/enterprise" ], - "python.defaultInterpreterPath": "/home/odoo/src/venv/bin/python3" + "python.defaultInterpreterPath": "/home/odoo/src/venv/bin/python3.12" } \ No newline at end of file diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py index e69de29bb2d..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9d7c41260c6..8a7a88130bc 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,29 @@ -from odoo import models +from odoo import models, fields -class TestModel(models.Model): - _name = "test_model" +class EstateProperty(models.Model): + + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(required = True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required = True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + string = "Orientation", + selection = [ + ('North', 'north'), + ('South', 'south'), + ('West', 'west'), + ('East', 'est') + ], + help = "Orientation of the garden" + ) From 25b1b9549c004481969ec5611ede5330594c036b Mon Sep 17 00:00:00 2001 From: dalio Date: Wed, 22 Apr 2026 10:05:16 +0200 Subject: [PATCH 04/28] [FIX] estate: fix style --- estate/models/estate_property.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8a7a88130bc..7b240fe44b9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,15 +1,16 @@ from odoo import models, fields + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(required = True) + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date() - expected_price = fields.Float(required = True) + expected_price = fields.Float(required=True) selling_price = fields.Float() bedrooms = fields.Integer() living_area = fields.Integer() @@ -18,12 +19,12 @@ class EstateProperty(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string = "Orientation", - selection = [ + string="Orientation", + selection=[ ('North', 'north'), ('South', 'south'), ('West', 'west'), ('East', 'est') ], - help = "Orientation of the garden" + help="Orientation of the garden" ) From d9454fb662cd7451719ac6d3354120f2c2ad71ce Mon Sep 17 00:00:00 2001 From: dalio Date: Wed, 22 Apr 2026 10:43:39 +0200 Subject: [PATCH 05/28] [ADD] estate: add access right for estate_property --- estate/__manifest__.py | 3 +++ estate/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e63b35840dd..998ed818881 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,5 +3,8 @@ 'depends': [ 'base_setup' ], + 'data': [ + 'security/ir.model.access.csv' + ], 'application': True } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..d9d6ba57cc5 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 From c76701593f5e44c7b3d3a80339d0797e1b12454a Mon Sep 17 00:00:00 2001 From: dalio Date: Wed, 22 Apr 2026 11:19:19 +0200 Subject: [PATCH 06/28] [FIX] estate: add author and license to manifest --- estate/__manifest__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 998ed818881..bfb173538c3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,13 @@ { 'name': 'Real Estate', + 'version': '1.0', 'depends': [ 'base_setup' ], 'data': [ 'security/ir.model.access.csv' ], - 'application': True + 'application': True, + 'author': 'dalio', + 'license': 'LGPL-3' } From 4faa6cd41873961ef18fbb8a2171e435c699e886 Mon Sep 17 00:00:00 2001 From: dalio Date: Wed, 22 Apr 2026 13:43:26 +0200 Subject: [PATCH 07/28] [ADD] estate: add menus, actions and property fields logic --- estate/__manifest__.py | 4 +++- estate/models/estate_property.py | 30 ++++++++++++++++++++------ estate/views/estate_menus.xml | 6 ++++++ estate/views/estate_property_views.xml | 8 +++++++ 4 files changed, 40 insertions(+), 8 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 bfb173538c3..e28eb65a2f0 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,7 +5,9 @@ 'base_setup' ], 'data': [ - 'security/ir.model.access.csv' + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' ], 'application': True, 'author': 'dalio', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7b240fe44b9..41097372c69 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import models, fields +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): @@ -9,10 +10,10 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(default=lambda self: fields.Date.today() + relativedelta(months=3), copy=False) 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,10 +22,25 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( string="Orientation", selection=[ - ('North', 'north'), - ('South', 'south'), - ('West', 'west'), - ('East', 'est') + ('north', 'North'), + ('south', 'South'), + ('west', 'West'), + ('east', 'East') ], help="Orientation of the garden" ) + active = fields.Boolean(default=True) + state = fields.Selection( + string="State", + selection=[ + ('new', 'New'), + ('offer received', 'Offer Received'), + ('offer accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], + help="State of the property", + 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..a941137bc57 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,6 @@ + + + + + + \ 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..affda3b8abc --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate.property + list,form + + \ No newline at end of file From c626a8c8dd3adc69b736131e9bc0a3f1dba5c921 Mon Sep 17 00:00:00 2001 From: dalio Date: Wed, 22 Apr 2026 16:42:59 +0200 Subject: [PATCH 08/28] [IMP] estate: add search features and apply style and security fixes --- .gitignore | 2 + .vscode/settings.json | 8 --- estate/__manifest__.py | 22 ++++---- estate/models/estate_property.py | 20 +++---- estate/security/ir.model.access.csv | 4 +- estate/views/estate_menus.xml | 10 ++-- estate/views/estate_property_views.xml | 78 ++++++++++++++++++++++++++ 7 files changed, 109 insertions(+), 35 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.gitignore b/.gitignore index b6e47617de1..e72a38f08c1 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 996e8684419..00000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "python.analysis.extraPaths": [ - "/home/odoo/src/venv", - "/home/odoo/src/odoo", - "/home/odoo/src/enterprise" - ], - "python.defaultInterpreterPath": "/home/odoo/src/venv/bin/python3.12" -} \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e28eb65a2f0..3b9694c64b1 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,15 +1,15 @@ { - 'name': 'Real Estate', - 'version': '1.0', - 'depends': [ - 'base_setup' + "name": "Real Estate", + "version": "19.0.1.0.0", + "depends": [ + "base", ], - '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", ], - 'application': True, - 'author': 'dalio', - 'license': 'LGPL-3' + "application": True, + "author": "dalio", + "license": "LGPL-3" } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 41097372c69..135e7431897 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import models, fields from dateutil.relativedelta import relativedelta +from odoo import models, fields class EstateProperty(models.Model): @@ -22,10 +22,10 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( string="Orientation", selection=[ - ('north', 'North'), - ('south', 'South'), - ('west', 'West'), - ('east', 'East') + ("north", "North"), + ("south", "South"), + ("west", "West"), + ("east", "East") ], help="Orientation of the garden" ) @@ -33,11 +33,11 @@ class EstateProperty(models.Model): state = fields.Selection( string="State", selection=[ - ('new', 'New'), - ('offer received', 'Offer Received'), - ('offer accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled') + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled") ], help="State of the property", required=True, diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d9d6ba57cc5..e358a621c01 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ -id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,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 +estate_property_access_user,estate.property.user,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index a941137bc57..efa13f06a78 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,6 +1,8 @@ - - - - \ No newline at end of file + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index affda3b8abc..5cc101805d1 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,4 +5,82 @@ estate.property list,form + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + \ No newline at end of file From f0442fbf24683860b9b4cca6651b318824bb3960 Mon Sep 17 00:00:00 2001 From: dalio Date: Thu, 23 Apr 2026 10:51:30 +0200 Subject: [PATCH 09/28] [ADD] estate: add types and tags --- .gitignore | 1 + estate/__manifest__.py | 2 ++ estate/models/__init__.py | 2 +- estate/models/estate_property.py | 3 +++ estate/models/estate_property_tag.py | 9 +++++++++ estate/models/estate_property_type.py | 9 +++++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_tag_views.xml | 7 +++++++ estate/views/estate_property_type_views.xml | 7 +++++++ estate/views/estate_property_views.xml | 10 +++++++++- 11 files changed, 57 insertions(+), 2 deletions(-) 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_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/.gitignore b/.gitignore index e72a38f08c1..8d9cce67e02 100644 --- a/.gitignore +++ b/.gitignore @@ -128,4 +128,5 @@ dmypy.json # Pyre type checker .pyre/ +# VSCode preferences .vscode/ diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3b9694c64b1..f12f7addcab 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,8 @@ ], "data": [ "security/ir.model.access.csv", + "views/estate_property_type_views.xml", + "views/estate_property_tag_views.xml", "views/estate_property_views.xml", "views/estate_menus.xml", ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..6c1ae061713 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property +from . import estate_property, estate_property_type, estate_property_tag diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 135e7431897..c12ff92b347 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -44,3 +44,6 @@ class EstateProperty(models.Model): copy=False, default="new" ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + salesman_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..ab642ee0122 --- /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 = "Property Tag" + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..60d61f0827c --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + + _name = "estate.property.type" + _description = "Property Type" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index e358a621c01..4e953854515 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate_property_access_user,estate.property.user,model_estate_property,base.group_user,1,1,1,1 +estate_property_type_access_user,estate.property.type.user,model_estate_property_type,base.group_user,1,1,1,1 +estate_property_tag_access_user,estate.property.tag.user,model_estate_property_tag,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index efa13f06a78..9fa93980dc0 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,8 +1,15 @@ + + + + + + + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..bbb5013739c --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,7 @@ + + + Property Tags + estate.property.tag + list,form + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..382925bd424 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,7 @@ + + + Property Types + estate.property.type + list,form + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5cc101805d1..1962c7ac709 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -35,6 +35,7 @@ + @@ -56,6 +57,12 @@ + + + + + + @@ -79,8 +86,9 @@ + - \ No newline at end of file + From 4ac7f3119ddccae5114a184ba025665a35149cf5 Mon Sep 17 00:00:00 2001 From: dalio Date: Thu, 23 Apr 2026 11:46:11 +0200 Subject: [PATCH 10/28] [ADD] estate: add offers to properties --- estate/__manifest__.py | 1 + estate/models/__init__.py | 2 +- estate/models/estate_property.py | 2 ++ estate/models/estate_property_offer.py | 19 ++++++++++++++ estate/models/estate_property_type.py | 2 +- estate/security/ir.model.access.csv | 1 + estate/views/estate_property_offer_views.xml | 27 ++++++++++++++++++++ estate/views/estate_property_views.xml | 4 +++ 8 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/views/estate_property_offer_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f12f7addcab..c563ad6159c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,7 @@ "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_menus.xml", ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 6c1ae061713..93a6bd86abd 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_type, estate_property_tag +from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c12ff92b347..0a2a7ebbbae 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -47,3 +47,5 @@ class EstateProperty(models.Model): property_type_id = fields.Many2one("estate.property.type", string="Property Type") salesman_id = fields.Many2one("res.users", string="Salesman", 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..76a78b2b282 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + + _name = "estate.property.offer" + _description = "Property Offer" + + price = fields.Float() + status = fields.Selection( + string="Status", + selection=[ + ("accepted", "Accepted"), + ("refused", "Refused") + ], + help="Status of the offer" + ) + 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_type.py b/estate/models/estate_property_type.py index 60d61f0827c..63487c82a45 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -2,7 +2,7 @@ class EstatePropertyType(models.Model): - + _name = "estate.property.type" _description = "Property Type" diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4e953854515..b2f2cb31b05 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,3 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate_property_access_user,estate.property.user,model_estate_property,base.group_user,1,1,1,1 estate_property_type_access_user,estate.property.type.user,model_estate_property_type,base.group_user,1,1,1,1 estate_property_tag_access_user,estate.property.tag.user,model_estate_property_tag,base.group_user,1,1,1,1 +estate_property_offer_access_user,estate.property.offer.user,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..c8ffa059ddf --- /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 + +
+ + + + + +
+
+
+
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1962c7ac709..7acea7acae2 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -35,6 +35,7 @@ + @@ -57,6 +58,9 @@ + + + From 3bd3578c533980e8381dd2f3485fd6bd41ee6791 Mon Sep 17 00:00:00 2001 From: dalio Date: Thu, 23 Apr 2026 16:16:58 +0200 Subject: [PATCH 11/28] [ADD] estate: add computed fields and onchanges --- estate/models/estate_property.py | 28 +++++++++++++++++++- estate/models/estate_property_offer.py | 21 ++++++++++++++- estate/views/estate_property_offer_views.xml | 4 +++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0a2a7ebbbae..29090e75c21 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from dateutil.relativedelta import relativedelta -from odoo import models, fields +from odoo import models, fields, api class EstateProperty(models.Model): @@ -49,3 +49,29 @@ class EstateProperty(models.Model): 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") + total_area = fields.Integer(compute="_compute_total_area", string="Total area (sqm)") + best_price = fields.Float(compute="_compute_best_price", string="Best Offer") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped("price") + + if prices: + record.best_price = max(prices) + else: + record.best_price = 0.0 + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 76a78b2b282..89aa6b7f1b6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import fields, models +from dateutil.relativedelta import relativedelta +from odoo import fields, models, api class EstatePropertyOffer(models.Model): @@ -17,3 +18,21 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one('res.partner', string="Partner", required=True) property_id = fields.Many2one('estate.property', string="Property", required=True) + validity = fields.Integer(default=7, string="Validity (days)") + date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", string="Deadline") + + @api.depends("create_date", "validity") + def _compute_date_deadline(self): + for record in self: + base_date = record.create_date or fields.Date.today() + days_to_add = record.validity or 0 + record.date_deadline = base_date + relativedelta(days=days_to_add) + + def _inverse_date_deadline(self): + for record in self: + base_date = fields.Date.to_date(record.create_date) or fields.Date.today() + + if record.date_deadline: + record.validity = (record.date_deadline - base_date).days + else: + record.validity = 0 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index c8ffa059ddf..ba0aa8ee1aa 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 7acea7acae2..fc400eeaba1 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -42,6 +42,7 @@ + @@ -56,6 +57,7 @@ + From 7061f54d77210407525cc0c10ee0bd52ac7f916b Mon Sep 17 00:00:00 2001 From: dalio Date: Thu, 23 Apr 2026 17:24:29 +0200 Subject: [PATCH 12/28] [ADD] estate: add 'Sold' and 'Canceled' buttons --- estate/models/estate_property.py | 15 +++++++++++++++ estate/models/estate_property_offer.py | 2 +- estate/views/estate_property_views.xml | 9 +++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 29090e75c21..3de31767053 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from dateutil.relativedelta import relativedelta from odoo import models, fields, api +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -75,3 +76,17 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + def action_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("Canceled properties cannot be sold.") + record.state = "sold" + return True + + def action_cancel(self): + for record in self: + if record.state == "sold": + raise UserError("Sold properties cannot be canceled.") + record.state = "cancelled" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 89aa6b7f1b6..cad7cda60d7 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -31,7 +31,7 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for record in self: base_date = fields.Date.to_date(record.create_date) or fields.Date.today() - + if record.date_deadline: record.validity = (record.date_deadline - base_date).days else: diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index fc400eeaba1..d2ed324d5cb 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -27,6 +27,14 @@ estate.property
+
+

@@ -35,6 +43,7 @@

+ From a3ebbc7b7acc103599cf3e51243281a5044c6266 Mon Sep 17 00:00:00 2001 From: dalio Date: Fri, 24 Apr 2026 10:33:10 +0200 Subject: [PATCH 13/28] [ADD] estate: add business logic with buttons and constraints --- estate/models/estate_property.py | 28 +++++++++++++++++++- estate/models/estate_property_offer.py | 17 ++++++++++++ estate/models/estate_property_tag.py | 5 ++++ estate/models/estate_property_type.py | 5 ++++ estate/views/estate_property_offer_views.xml | 2 ++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3de31767053..f2338517c76 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import models, fields, api -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_is_zero, float_compare class EstateProperty(models.Model): @@ -90,3 +91,28 @@ def action_cancel(self): raise UserError("Sold properties cannot be canceled.") record.state = "cancelled" return True + + _check_expected_price = models.Constraint( + "CHECK(expected_price > 0)", + "the expected price must be strictly positive.", + ) + + _check_selling_price = models.Constraint( + "CHECK(selling_price >= 0)", + "the selling price must be positive.", + ) + + @api.constrains("selling_price", "expected_price") + def _check_selling_price(self): + for record in self: + + if float_is_zero(record.selling_price, precision_digits=2): + continue + + lower_bound = 0.9 * record.expected_price + + if float_compare(record.selling_price, lower_bound, precision_digits=2) == -1: + raise ValidationError( + "The selling price must be at least 90% of the expected price! " + "You must reduce the expected price if you want to accept this offer." + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index cad7cda60d7..7c92b076e73 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -36,3 +36,20 @@ def _inverse_date_deadline(self): record.validity = (record.date_deadline - base_date).days else: record.validity = 0 + + def action_accept(self): + for record in self: + record.status = "accepted" + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + return True + + def action_refuse(self): + for record in self: + record.status = "refused" + return True + + _check_price = models.Constraint( + "CHECK(price > 0)", + "the offer price must be strictly positive.", + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index ab642ee0122..a4f9d8389d5 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -7,3 +7,8 @@ class EstatePropertyTag(models.Model): _description = "Property Tag" name = fields.Char(required=True) + + _unique_name = models.Constraint( + "UNIQUE(name)", + "the name must be unique.", + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 63487c82a45..edbe216a026 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -7,3 +7,8 @@ class EstatePropertyType(models.Model): _description = "Property Type" name = fields.Char(required=True) + + _unique_name = models.Constraint( + "UNIQUE(name)", + "the name must be unique.", + ) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index ba0aa8ee1aa..5bc3c72e3d9 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + +

@@ -31,4 +37,15 @@ + + + estate.property.type.list + estate.property.type + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index bcd44b6f946..49c59d35d76 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,20 +4,21 @@ Properties estate.property list,form + {'search_default_available': True} estate.property.list estate.property - - + + - + @@ -26,7 +27,7 @@ estate.property.form estate.property -
+

- - + + @@ -61,13 +62,13 @@ - - + +
- + @@ -86,12 +87,12 @@ estate.property.search estate.property - + - + From 6a5844e525cc6450b6e69fda33b4b7b711c43f69 Mon Sep 17 00:00:00 2001 From: dalio Date: Mon, 27 Apr 2026 16:13:01 +0200 Subject: [PATCH 17/28] [FIX] estate: add readonly to invisible status field in offer list --- estate/views/estate_property_offer_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 0ce2857f92e..4000a7def61 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -11,7 +11,7 @@ + + + + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..db93191361d 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,20 @@ -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_list/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + static props = {}; + + setup() { + this.state = useState({ sum: 2 }); + this.normalString = "
some content
" + this.markupString = markup("
some content
"); + } + + incrementSum() { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..b58c6df0ee1 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,25 @@ - +
+ + + Sum: +
+ +

My Cards

+
+ +
+
+
- hello world +
+
+
+ +
+
-
diff --git a/awesome_owl/static/src/todo_item/todo_item.js b/awesome_owl/static/src/todo_item/todo_item.js new file mode 100644 index 00000000000..462f2012a83 --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.js @@ -0,0 +1,16 @@ +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, + }, + }, + }; +} \ No newline at end of file diff --git a/awesome_owl/static/src/todo_item/todo_item.xml b/awesome_owl/static/src/todo_item/todo_item.xml new file mode 100644 index 00000000000..80c85d58507 --- /dev/null +++ b/awesome_owl/static/src/todo_item/todo_item.xml @@ -0,0 +1,10 @@ + + + +
+ + . + +
+
+
\ No newline at end of file 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..864c6f31859 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,33 @@ +import { Component, useState, useRef } from "@odoo/owl"; +import { TodoItem } from "../todo_item/todo_item"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + static components = { TodoItem }; + static props = {}; + + setup() { + this.todos = useState([]); + this.inputRef = useRef("todoInput"); + this.nextId = 1; + } + + addTodo(ev) { + + if (ev.keyCode === 13 ) { + + const description = ev.target.value.trim(); + + if (description !== "") { + this.todos.push({ + id: this.nextId++, + description: description, + isCompleted: false, + }); + + ev.target.value = ""; + + } + } + } +} \ No newline at end of file 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..cd29795befc --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,22 @@ + + + +
+

Todo List

+ +
+ +
+ +
+ + + +
+
+
+
\ No newline at end of file From 11820f7eab79cdcfd802f089a4339baf6eb7e7d8 Mon Sep 17 00:00:00 2001 From: "Dario Liotta (dalio)" Date: Thu, 30 Apr 2026 13:23:14 +0200 Subject: [PATCH 27/28] [IMP] estate: apply suggestions for performance and constraints --- estate/models/estate_property.py | 10 +++++----- estate/models/estate_property_offer.py | 10 ++++++---- estate/models/estate_property_type.py | 8 ++++---- estate_account/__manifest__.py | 1 - 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3af9844534e..2d0f75afab9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -14,8 +14,8 @@ class EstateProperty(models.Model): description = fields.Text() postcode = fields.Char() date_availability = fields.Date(default=lambda self: fields.Date.today() + relativedelta(months=3), copy=False) - expected_price = fields.Monetary(currency_field="currency_id", required=True) - selling_price = fields.Monetary(currency_field="currency_id", readonly=True, copy=False) + expected_price = fields.Monetary(required=True) + selling_price = fields.Monetary(readonly=True, copy=False) bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() @@ -105,19 +105,19 @@ def _onchange_garden(self): @api.ondelete(at_uninstall=False) def _check_state_before_deletion(self): for record in self: - if record.state not in ["new", "canceled"]: + if record.state not in ["new", "cancelled"]: raise UserError("Only new and canceled properties can be deleted.") def action_sold(self): for record in self: if record.state == "cancelled": raise UserError("Canceled properties cannot be sold.") - record.state = "sold" + self.state = "sold" return True def action_cancel(self): for record in self: if record.state == "sold": raise UserError("Sold properties cannot be canceled.") - record.state = "cancelled" + self.state = "cancelled" return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 6db676fc21e..20735b3862f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -9,9 +9,8 @@ class EstatePropertyOffer(models.Model): _description = "Property Offer" _order = "price desc" - price = fields.Monetary(currency_field="currency_id") + price = fields.Monetary() status = fields.Selection( - string="Status", selection=[ ("accepted", "Accepted"), ("refused", "Refused") @@ -48,8 +47,11 @@ def _inverse_date_deadline(self): @api.model def create(self, vals_list): - for vals in vals_list: - prop = self.env["estate.property"].browse(vals.get("property_id")) + property_ids = [vals.get("property_id") for vals in vals_list if vals.get("property_id")] + properties = self.env["estate.property"].browse(property_ids) + + for vals, prop in zip(vals_list, properties): + existing_prices = prop.offer_ids.mapped("price") if existing_prices and vals.get("price") < max(existing_prices): diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 54b435dbcb5..6be3d3cb58b 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -5,15 +5,15 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Property Type" - _order = "name" + _order = "sequence, name" name = fields.Char(required=True) sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") - property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") - offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") + property_ids = fields.One2many("estate.property", "property_type_id") + offer_ids = fields.One2many("estate.property.offer", "property_type_id") - offer_count = fields.Integer(compute="_compute_offer_count", string="Offer Count") + offer_count = fields.Integer(compute="_compute_offer_count") _unique_name = models.Constraint( "UNIQUE(name)", diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 9d41585d847..9e670884446 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -6,7 +6,6 @@ "account", ], "data": [], - "application": False, "installable": True, "author": "dalio", "license": "LGPL-3" From 24f482b3f7529f4c32a7f0de2ea51ce3334f397b Mon Sep 17 00:00:00 2001 From: "Dario Liotta (dalio)" Date: Thu, 30 Apr 2026 15:27:07 +0200 Subject: [PATCH 28/28] [ADD] awesome_owl: add counters in cards --- awesome_owl/static/src/card/card.js | 19 ++++++++++--- awesome_owl/static/src/card/card.xml | 20 ++++++++------ awesome_owl/static/src/counter/counter.xml | 16 +++++------ awesome_owl/static/src/playground.xml | 26 +++++++++++------- awesome_owl/static/src/todo_item/todo_item.js | 12 ++++++++- .../static/src/todo_item/todo_item.xml | 4 ++- awesome_owl/static/src/todo_list/todo_list.js | 27 ++++++++++++++----- .../static/src/todo_list/todo_list.xml | 4 +-- awesome_owl/static/src/utils.js | 14 ++++++++++ 9 files changed, 103 insertions(+), 39 deletions(-) create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index e80226daa58..26edae09b61 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -1,10 +1,23 @@ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; export class Card extends Component { static template = "awesome_owl.Card"; static props = { - title: { type: String }, - content: { type: String }, + title: String, + slots: { + type: Object, + shape: { + default: Object, + }, + }, }; + + 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 index e4171dda82a..46744c65ec3 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -1,14 +1,18 @@ -
-
-
- -
-

- -

+
+
+
+ +
+ +
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml index a18f341b33a..f31200276ce 100644 --- a/awesome_owl/static/src/counter/counter.xml +++ b/awesome_owl/static/src/counter/counter.xml @@ -1,15 +1,13 @@ -
-
- - Counter: - - -
+
+ + Counter: + +
diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index b58c6df0ee1..18e32b06070 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,23 +1,31 @@ -
+
+
- Sum: + Sum:
-

My Cards

-
- +
+ +

Simple content.

+
+ + +
+ +
+
-
-

-
+
- +
+ +
diff --git a/awesome_owl/static/src/todo_item/todo_item.js b/awesome_owl/static/src/todo_item/todo_item.js index 462f2012a83..09ad8001e0a 100644 --- a/awesome_owl/static/src/todo_item/todo_item.js +++ b/awesome_owl/static/src/todo_item/todo_item.js @@ -12,5 +12,15 @@ export class TodoItem extends Component { isCompleted: Boolean, }, }, + toggleState: Function, + removeTodo: Function, }; -} \ No newline at end of file + + onChange() { + this.props.toggleState(this.props.todo.id); + } + + onRemove() { + this.props.removeTodo(this.props.todo.id); + } +} diff --git a/awesome_owl/static/src/todo_item/todo_item.xml b/awesome_owl/static/src/todo_item/todo_item.xml index 80c85d58507..c5b8123ce87 100644 --- a/awesome_owl/static/src/todo_item/todo_item.xml +++ b/awesome_owl/static/src/todo_item/todo_item.xml @@ -2,9 +2,11 @@
+ . +
-
\ No newline at end of file + diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js index 864c6f31859..1054944bc21 100644 --- a/awesome_owl/static/src/todo_list/todo_list.js +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -1,5 +1,6 @@ -import { Component, useState, useRef } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; import { TodoItem } from "../todo_item/todo_item"; +import { useAutofocus } from "../utils"; export class TodoList extends Component { static template = "awesome_owl.TodoList"; @@ -8,8 +9,15 @@ export class TodoList extends Component { setup() { this.todos = useState([]); - this.inputRef = useRef("todoInput"); - this.nextId = 1; + this.nextId = 0; + this.inputRef = useAutofocus("todoInput"); + } + + toggleTodo(todoId) { + const todo = this.todos.find((t) => t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } } addTodo(ev) { @@ -21,13 +29,20 @@ export class TodoList extends Component { if (description !== "") { this.todos.push({ id: this.nextId++, - description: description, + description, isCompleted: false, }); ev.target.value = ""; - + } } } -} \ No newline at end of file + + 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 index cd29795befc..2467b811ebb 100644 --- a/awesome_owl/static/src/todo_list/todo_list.xml +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -14,9 +14,9 @@
- +
- \ No newline at end of file + diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..9bd8e1631e7 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,14 @@ +import { useRef, onMounted } from "@odoo/owl"; + +export function useAutofocus(name) { + + const ref = useRef(name); + + onMounted(() => { + if (ref.el) { + ref.el.focus(); + } + }); + + return ref; +}