From e1d798241f54e682ebb2f4a6e4cc0295397865e8 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 17 Aug 2026 13:17:31 +0200 Subject: [PATCH 01/26] added init and manifest --- estate/__init__.py | 0 estate/__manifest__.py | 7 +++++++ 2 files changed, 7 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..bcdebbdb54f --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,7 @@ +{ + 'name': 'estate-kehey', + 'depends': [ + 'base', + ], + 'application':True +} \ No newline at end of file From cb84f6e0a1a9f3a55002923041fa0917000404f5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 17 Aug 2026 15:55:26 +0200 Subject: [PATCH 02/26] added all the functionality until the basic views of server framework --- .gitignore | 2 ++ estate/__init__.py | 1 + estate/__manifest__.py | 10 ++++++++- estate/data/estate_menus.xml | 8 +++++++ estate/data/ir.model.access.csv | 2 ++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 30 ++++++++++++++++++++++++++ estate/views/estate_property_views.xml | 8 +++++++ 8 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 estate/data/estate_menus.xml create mode 100644 estate/data/ir.model.access.csv create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/views/estate_property_views.xml diff --git a/.gitignore b/.gitignore index b6e47617de1..eb45fe797be 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ dmypy.json # Pyre type checker .pyre/ + +.vscode 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/__manifest__.py b/estate/__manifest__.py index bcdebbdb54f..33c4024113e 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,6 +2,14 @@ 'name': 'estate-kehey', 'depends': [ 'base', + ], + 'data': [ + 'data/ir.model.access.csv', + 'views/estate_property_views.xml', + 'data/estate_menus.xml' + ], + 'views': [ + ], 'application':True -} \ No newline at end of file +} diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml new file mode 100644 index 00000000000..ff0f6530656 --- /dev/null +++ b/estate/data/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv new file mode 100644 index 00000000000..8efbc0866c3 --- /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_test_model,access_test_model,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ 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 new file mode 100644 index 00000000000..e91bffb1693 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,30 @@ +from odoo import fields, models +from dateutil.relativedelta import relativedelta + +class TestModel(models.Model): + _name = "estate_property" + _description = "test estate model" + + name = fields.Char(required=True) + active = fields.Boolean(default=True) + state = fields.Selection( + required=True, + copy=False, + default="new", + selection=[("new", "New"), ("offer received", "Offer Received"), ("offer accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled","Cancelled")] + ) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Datetime(copy=False, default=fields.Datetime.today() + (relativedelta(months=3))) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + string='type', + selection=[('north', 'North'), ('south', 'South'), ('East', 'east'), ('West', 'west')] + ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..c2a0fc920ac --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate_property + list,form + + From 99af35bec0310915a6de2ec1e50ad5486903b983 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 17 Aug 2026 17:25:14 +0200 Subject: [PATCH 03/26] end of day 1 --- estate/views/estate_property_views.xml | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c2a0fc920ac..e05b6e05e86 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,4 +5,68 @@ estate_property list,form + + + estate.property.list + estate_property + + + + + + + + + + + + + + + estate.property.form + estate_property + +
+ +
+
+
+

+ +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
From a969dc26fa2d54b7a909b7c201c728586d17feb4 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 18 Aug 2026 09:52:01 +0200 Subject: [PATCH 04/26] server framework 101 end of chapter 6 --- estate/models/estate_property.py | 2 +- estate/views/estate_property_views.xml | 32 +++++++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e91bffb1693..4521e0b76ad 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,7 +2,7 @@ from dateutil.relativedelta import relativedelta class TestModel(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "test estate model" name = fields.Char(required=True) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index e05b6e05e86..8dd3da4209a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,14 +1,27 @@ - - Properties - estate_property - list,form + + estate.property.search + estate.property + + + + + + + + + + + + + + estate.property.list - estate_property + estate.property @@ -24,7 +37,7 @@ estate.property.form - estate_property + estate.property
@@ -62,6 +75,7 @@ + @@ -69,4 +83,10 @@
+ + + Properties + estate.property + list,form +
From 031009b81b58cd1ed44ac34e3a959e9418b7ed07 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 18 Aug 2026 15:17:32 +0200 Subject: [PATCH 05/26] finished server framework 101 chapter 9 --- estate/__manifest__.py | 1 + estate/data/estate_menus.xml | 4 ++ estate/data/ir.model.access.csv | 5 ++- estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 38 +++++++++++++++++-- estate/models/estate_property_offer.py | 31 +++++++++++++++ estate/models/estate_property_tag.py | 7 ++++ estate/models/estate_property_type.py | 7 ++++ .../views/estate_property_settings_views.xml | 25 ++++++++++++ estate/views/estate_property_views.xml | 25 ++++++++++++ 10 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_settings_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 33c4024113e..f497aa3dd9d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,7 @@ 'data': [ 'data/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_settings_views.xml', 'data/estate_menus.xml' ], 'views': [ diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml index ff0f6530656..32d4493e75f 100644 --- a/estate/data/estate_menus.xml +++ b/estate/data/estate_menus.xml @@ -4,5 +4,9 @@ + + + + diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv index 8efbc0866c3..9d6c730946d 100644 --- a/estate/data/ir.model.access.csv +++ b/estate/data/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_test_model,access_test_model,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,access_estate_property_user,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,access_estate_property_user,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,access_estate_property_user,model_estate_property_offer,base.group_user,1,1,1,1 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 4521e0b76ad..5bd4c0c80cd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,11 +1,15 @@ -from odoo import fields, models +from odoo import fields, models, api from dateutil.relativedelta import relativedelta -class TestModel(models.Model): +class EstateProperty(models.Model): _name = "estate.property" - _description = "test estate model" - + _description = "estate model" name = fields.Char(required=True) + salesman_id = fields.Many2one("res.partner", string="Salesman") + buyer_id = fields.Many2one("res.users", default=lambda self: self.env.user, string="Buyer") + type_id = fields.Many2one("estate.property.type") + tags_id = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") active = fields.Boolean(default=True) state = fields.Selection( required=True, @@ -28,3 +32,29 @@ class TestModel(models.Model): string='type', selection=[('north', 'North'), ('south', 'South'), ('East', 'east'), ('West', 'west')] ) + total_area = fields.Integer(string="Total Area", compute="_compute_total_surface") + best_offer = fields.Float(string="Best Offer", compute="_compute_best_offer") + + + #==========computed fields=============== + @api.depends('garden_area', 'living_area') + def _compute_total_surface(self): + for record in self: + record.total_area = record.garden_area + record.living_area + + @api.depends('offer_ids') + def _compute_best_offer(self): + for record in self: + record.best_offer = max(record.offer_ids.mapped("price")) + + #============onchage fields============== + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = False + self.garden_orientation = False + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..f5057191ada --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,31 @@ +from odoo import api, fields, models +from dateutil.relativedelta import relativedelta + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "estate offer model" + + name = fields.Char(required=True) + price = fields.Float() + status = fields.Selection( + string='status', + copy=False, + selection=[('accepted', 'Accepted'), ('refused', 'Refused')] + ) + partner_id = fields.Many2one("res.partner", required=True) + property_id = fields.Many2one("estate.property", required=True) + date_deadline = fields.Datetime(string="Deadline", compute="compute_deadline", inverse="_inverse_deadline") + validity = fields.Integer(string="validity", default=7) + + @api.depends('validity', "create_date") + def compute_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = record.create_date + relativedelta(days=record.validity) + else: + record.date_deadline = fields.Datetime.today() + relativedelta(days=record.validity) + + def _inverse_deadline(self): + for record in self: + record.validity = (record.date_deadline - record.create_date).days + diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..08014443cd5 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "estate tag model" + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..84f617edc94 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "estate type model" + + name = fields.Char() diff --git a/estate/views/estate_property_settings_views.xml b/estate/views/estate_property_settings_views.xml new file mode 100644 index 00000000000..9d5f8e35343 --- /dev/null +++ b/estate/views/estate_property_settings_views.xml @@ -0,0 +1,25 @@ + + + + + estate.property.type.list + estate.property.type + + + + + + + + + Property Types + estate.property.type + list,form + + + + Property Tags + estate.property.tag + list,form + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8dd3da4209a..d2a15211486 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -51,6 +51,12 @@ + + + + + + @@ -67,6 +73,7 @@ + @@ -74,10 +81,28 @@ + + + + + + + + + + + + + + + + + + From fa0cc1c98bb67d68c1fdd1e1f3916d53c031bc65 Mon Sep 17 00:00:00 2001 From: Kevin Date: Tue, 18 Aug 2026 16:30:46 +0200 Subject: [PATCH 06/26] actually finished server framework 101 chapter 9 --- estate/models/estate_property.py | 18 ++++++++++++++++-- estate/models/estate_property_offer.py | 18 ++++++++++++++++-- estate/views/estate_property_views.xml | 6 ++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 5bd4c0c80cd..390d9d3e4db 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models, api +from odoo import exceptions, fields, models, api from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): @@ -56,5 +56,19 @@ def _onchange_garden(self): else: self.garden_area = False self.garden_orientation = False + #==========button functions============== + def action_property_sold(self): + for record in self: + if record.state == "cancelled": + raise exceptions.UserError("Can't sell a cancelled property") + else: + record.state = "sold" + return True - + def action_property_cancelled(self): + for record in self: + if record.state == "sold": + raise exceptions.UserError("Can't cancel a sold property") + else: + record.state = "cancelled" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index f5057191ada..800ed151e85 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import exceptions, api, fields, models from dateutil.relativedelta import relativedelta class EstatePropertyOffer(models.Model): @@ -12,7 +12,7 @@ class EstatePropertyOffer(models.Model): copy=False, selection=[('accepted', 'Accepted'), ('refused', 'Refused')] ) - partner_id = fields.Many2one("res.partner", required=True) + partner_id = fields.Many2one("res.users", required=True) property_id = fields.Many2one("estate.property", required=True) date_deadline = fields.Datetime(string="Deadline", compute="compute_deadline", inverse="_inverse_deadline") validity = fields.Integer(string="validity", default=7) @@ -29,3 +29,17 @@ def _inverse_deadline(self): for record in self: record.validity = (record.date_deadline - record.create_date).days + #===========button actions=========== + def action_accept(self): + + for record in self: + if "accepted" in record.property_id.offer_ids.mapped("status"): + raise exceptions.UserError("already accepted an offer!") + record.property_id.buyer_id = record.partner_id + record.property_id.selling_price = record.price + record.status = "accepted" + + def action_refuse(self): + for record in self: + for property in record.property_id: + record.status = "refused" diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index d2a15211486..a4cb5799b75 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -40,6 +40,10 @@ estate.property
+
+
@@ -94,6 +98,8 @@ +

- +

- - - @@ -137,7 +170,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_state': True} From 721c3c7a7f7fea9f8cd2529377f65f39b2da7277 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 20 Aug 2026 13:15:59 +0200 Subject: [PATCH 12/26] fixed styling changes --- estate/__init__.py | 1 + estate/__manifest__.py | 4 +- estate/data/estate_menus.xml | 1 + estate/data/ir.model.access.csv | 1 + estate/models/__init__.py | 12 +++--- estate/models/estate_property.py | 37 ++++++++++--------- estate/models/estate_property_offer.py | 22 ++++++----- estate/models/estate_property_tag.py | 1 + estate/models/inhereted_users.py | 1 + .../views/estate_property_settings_views.xml | 1 + estate/views/estate_property_views.xml | 1 + estate/views/users_extra_views.xml | 1 + 12 files changed, 48 insertions(+), 35 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..899bcc97f0f 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ from . import models + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 149d269bb7a..3def0192dc6 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,9 +9,7 @@ 'views/estate_property_settings_views.xml', 'views/users_extra_views.xml', 'data/estate_menus.xml' - ], - 'views': [ - ], 'application':True } + diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml index 32d4493e75f..112da7f4918 100644 --- a/estate/data/estate_menus.xml +++ b/estate/data/estate_menus.xml @@ -10,3 +10,4 @@
+ diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv index 9d6c730946d..a6a34c6144d 100644 --- a/estate/data/ir.model.access.csv +++ b/estate/data/ir.model.access.csv @@ -3,3 +3,4 @@ access_estate_property_user,access_estate_property_user,model_estate_property,ba access_estate_property_type_user,access_estate_property_user,model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_tag_user,access_estate_property_user,model_estate_property_tag,base.group_user,1,1,1,1 access_estate_property_offer_user,access_estate_property_user,model_estate_property_offer,base.group_user,1,1,1,1 + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index bd2c94a0b8e..c7063f5d755 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,5 +1,7 @@ -from . import estate_property -from . import estate_property_type -from . import estate_property_tag -from . import estate_property_offer -from . import inhereted_users +from . import ( + estate_property, + estate_property_offer, + estate_property_tag, + estate_property_type, + inhereted_users, +) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 897fb3b1f9f..1b3c26e4ab7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,8 @@ -from odoo import exceptions, fields, models, api from dateutil.relativedelta import relativedelta +from odoo import api, exceptions, fields, models + + class EstateProperty(models.Model): _name = "estate.property" _description = "estate model" @@ -17,7 +19,7 @@ class EstateProperty(models.Model): required=True, copy=False, default="new", - selection=[("new", "New"), ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled","Cancelled")] + selection=[("new", "New"), ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")], ) description = fields.Text() postcode = fields.Char() @@ -32,12 +34,12 @@ class EstateProperty(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( string='type', - selection=[('north', 'North'), ('south', 'South'), ('East', 'east'), ('West', 'west')] + selection=[('north', 'North'), ('south', 'South'), ('East', 'east'), ('West', 'west')], ) total_area = fields.Integer(string="Total Area", compute="_compute_total_surface") best_offer = fields.Float(string="Best Offer", compute="_compute_best_offer") - #==========constraints=================== + # ==========constraints=================== _check_positive_expected_price = models.Constraint("CHECK (expected_price > 0)", "expected price should be bigger than 0") _check_positive_selling_price = models.Constraint("CHECK (selling_price > 0)", "expected price should be bigger than 0") @@ -47,9 +49,10 @@ def _check_enough_selling_price(self): offer_made = "accepted" in record.offer_ids.mapped("status") price_good_enough = record.selling_price > 0.9 * record.expected_price if not price_good_enough and offer_made: - raise exceptions.ValidationError("selling price is too low for the expected price") + to_low_user_error = "selling price is too low for the expected price" + raise exceptions.ValidationError(to_low_user_error) - #==========computed fields=============== + # ==========computed fields=============== @api.depends('garden_area', 'living_area') def _compute_total_surface(self): for record in self: @@ -63,7 +66,7 @@ def _compute_best_offer(self): else: record.best_offer = max(record.offer_ids.mapped("price")) - #============onchage fields============== + # ============onchage fields============== @api.onchange("garden") def _onchange_garden(self): if self.garden: @@ -72,27 +75,27 @@ def _onchange_garden(self): else: self.garden_area = False self.garden_orientation = False - #==========button functions============== + + # ==========button functions============== def action_property_sold(self): for record in self: if record.state == "cancelled": - raise exceptions.UserError("Can't sell a cancelled property") - else: - record.state = "sold" + no_sell_cancelled_error = "Can't sell a cancelled property" + raise exceptions.UserError(no_sell_cancelled_error) + record.state = "sold" return True def action_property_cancelled(self): for record in self: if record.state == "sold": - raise exceptions.UserError("Can't cancel a sold property") - else: - record.state = "cancelled" + no_sell_a_sold_property = "Can't cancel a sold property" + raise exceptions.UserError(no_sell_a_sold_property) + record.state = "cancelled" return True @api.ondelete(at_uninstall=False) def ondelete(self): for property in self: if property.state in ("new", "cancelled"): - raise exceptions.UserError("cannot delete new or cancelled record") - - + no_delete_new_or_cancelled_record = "cannot delete new or cancelled record" + raise exceptions.UserError(no_delete_new_or_cancelled_record) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b63970c2e6e..5c2db7ea6cd 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,8 @@ -from odoo import exceptions, api, fields, models from dateutil.relativedelta import relativedelta +from odoo import api, exceptions, fields, models + + class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "estate offer model" @@ -11,7 +13,7 @@ class EstatePropertyOffer(models.Model): status = fields.Selection( string='status', copy=False, - selection=[('accepted', 'Accepted'), ('refused', 'Refused')] + selection=[('accepted', 'Accepted'), ('refused', 'Refused')], ) partner_id = fields.Many2one("res.users", required=True) property_id = fields.Many2one("estate.property", required=True, ondelete="cascade") @@ -19,10 +21,9 @@ class EstatePropertyOffer(models.Model): validity = fields.Integer(string="validity", default=7) property_type_id = fields.Many2one("estate.property.type", related="property_id.type_id", string="Property Type", store=True) - #=========contraints============ + # =========contraints============ _check_positive_offer_price = models.Constraint("CHECK (price > 0)", "expected price should be bigger than 0") - @api.depends('validity', "create_date") def compute_deadline(self): for record in self: @@ -30,23 +31,23 @@ def compute_deadline(self): record.date_deadline = record.create_date + relativedelta(days=record.validity) else: record.date_deadline = fields.Datetime.today() + relativedelta(days=record.validity) - + def _inverse_deadline(self): for record in self: record.validity = (record.date_deadline - record.create_date).days - #===========button actions=========== + # ===========button actions=========== def action_accept(self): for record in self: if "accepted" in record.property_id.offer_ids.mapped("status"): - raise exceptions.UserError("already accepted an offer!") + already_accepted_exception = "already accepted an offer!" + raise exceptions.UserError(already_accepted_exception) record.property_id.buyer_id = record.partner_id record.property_id.state = "offer_accepted" record.status = "accepted" record.property_id.selling_price = record.price - - + def action_refuse(self): for record in self: for property in record.property_id: @@ -59,6 +60,7 @@ def create(self, vals): new_bid = to_create["price"] for offer in property.offer_ids: if offer.price > new_bid: - raise exceptions.UserError("can't bid lower than the highest bid") + cant_bit_lower_exception = "can't bid lower than the highest bid" + raise exceptions.UserError(cant_bit_lower_exception) property.state = "offer_received" return super().create(vals) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index c5c62eca257..90981f38468 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -9,3 +9,4 @@ class EstatePropertyTag(models.Model): _check_unique_tag = models.Constraint("UNIQUE(name)", "tags should be unique") + diff --git a/estate/models/inhereted_users.py b/estate/models/inhereted_users.py index 0885165909e..08ba8c665d0 100644 --- a/estate/models/inhereted_users.py +++ b/estate/models/inhereted_users.py @@ -7,3 +7,4 @@ class InheritedModel(models.Model): "estate.property", "salesman_id", domain=[("state", "in", ["new", "offer_received"])]) + diff --git a/estate/views/estate_property_settings_views.xml b/estate/views/estate_property_settings_views.xml index b3e0bdd4ef8..26dd1d1aa4b 100644 --- a/estate/views/estate_property_settings_views.xml +++ b/estate/views/estate_property_settings_views.xml @@ -59,3 +59,4 @@ list,form + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index eea40e9c84f..4a460a0954c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -177,3 +177,4 @@ + diff --git a/estate/views/users_extra_views.xml b/estate/views/users_extra_views.xml index 71fcb43cd16..a984be48ae4 100644 --- a/estate/views/users_extra_views.xml +++ b/estate/views/users_extra_views.xml @@ -13,3 +13,4 @@ + From 873b4657fb2923186a5f5b7f7b6593193f28cb1d Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 20 Aug 2026 13:31:24 +0200 Subject: [PATCH 13/26] ammended styling fix --- estate/models/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c7063f5d755..819bd60fb3b 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,7 +1,6 @@ -from . import ( - estate_property, - estate_property_offer, - estate_property_tag, - estate_property_type, - inhereted_users, -) +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer +from . import inhereted_users + From 65c6202295487060fae3e4dd75f160ad1a4a2920 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 21 Aug 2026 09:10:13 +0200 Subject: [PATCH 14/26] [ADD] estate: added test cases for estate class prevent other people from breaking the reset of the estate's garden area and orientation when a garden is present --- estate/__init__.py | 1 - estate/__manifest__.py | 5 +- estate/models/__init__.py | 12 +++-- estate/models/estate_property.py | 11 +++- estate/models/estate_property_offer.py | 10 +++- estate/models/estate_property_tag.py | 3 +- estate/models/estate_property_type.py | 2 +- estate/models/inhereted_users.py | 4 +- estate/tests/__init__.py | 1 + estate/tests/test_estate.py | 69 ++++++++++++++++++++++++ estate_account/__manifest__.py | 6 +-- estate_account/models/estate_property.py | 9 ++-- 12 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 estate/tests/__init__.py create mode 100644 estate/tests/test_estate.py diff --git a/estate/__init__.py b/estate/__init__.py index 899bcc97f0f..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1 @@ from . import models - diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3def0192dc6..606e037153e 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,8 +8,7 @@ 'views/estate_property_views.xml', 'views/estate_property_settings_views.xml', 'views/users_extra_views.xml', - 'data/estate_menus.xml' + 'data/estate_menus.xml', ], - 'application':True + 'application': True, } - diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 819bd60fb3b..f8ae845d7ac 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,6 +1,8 @@ -from . import estate_property -from . import estate_property_type -from . import estate_property_tag -from . import estate_property_offer -from . import inhereted_users +from . import ( + estate_property, + estate_property_offer, + estate_property_tag, + estate_property_type, + inhereted_users, +) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1b3c26e4ab7..4ebf83d7cce 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -52,6 +52,13 @@ def _check_enough_selling_price(self): to_low_user_error = "selling price is too low for the expected price" raise exceptions.ValidationError(to_low_user_error) + @api.constrains("state") + def _no_sell_without_offer(self): + for record in self: + if record.state == "sold" and "accepted" not in record.offer_ids.mapped("status"): + only_sold_if_accepted = "can only sell a property with an accepted offer" + raise exceptions.UserError(only_sold_if_accepted) + # ==========computed fields=============== @api.depends('garden_area', 'living_area') def _compute_total_surface(self): @@ -73,8 +80,8 @@ def _onchange_garden(self): self.garden_area = 10 self.garden_orientation = "north" else: - self.garden_area = False - self.garden_orientation = False + self.garden_area = 0 + self.garden_orientation = "False" # ==========button functions============== def action_property_sold(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5c2db7ea6cd..328bd4be46b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -15,7 +15,7 @@ class EstatePropertyOffer(models.Model): copy=False, selection=[('accepted', 'Accepted'), ('refused', 'Refused')], ) - partner_id = fields.Many2one("res.users", required=True) + partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True, ondelete="cascade") date_deadline = fields.Datetime(string="Deadline", compute="compute_deadline", inverse="_inverse_deadline") validity = fields.Integer(string="validity", default=7) @@ -57,9 +57,15 @@ def action_refuse(self): def create(self, vals): for to_create in vals: property = self.env["estate.property"].browse(to_create["property_id"]) + # If the property is already sold we can't add it + if property.state == "sold": + no_create_on_sold = "no create on already sold property" + raise exceptions.UserError(no_create_on_sold) + + # Set a minimum bidding limit new_bid = to_create["price"] for offer in property.offer_ids: - if offer.price > new_bid: + if offer.price > float(new_bid): cant_bit_lower_exception = "can't bid lower than the highest bid" raise exceptions.UserError(cant_bit_lower_exception) property.state = "offer_received" diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 90981f38468..de7a90154b1 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,5 +1,6 @@ from odoo import fields, models + class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "estate tag model" @@ -7,6 +8,4 @@ class EstatePropertyTag(models.Model): name = fields.Char(required=True) - _check_unique_tag = models.Constraint("UNIQUE(name)", "tags should be unique") - diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 7289b9dcc4f..c44ccf2cdfc 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,5 +1,6 @@ from odoo import api, fields, models + class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "estate type model" @@ -17,4 +18,3 @@ class EstatePropertyType(models.Model): def _compute_offer_count(self): for property_type_record in self: property_type_record.offer_count = len(property_type_record.offer_ids) - diff --git a/estate/models/inhereted_users.py b/estate/models/inhereted_users.py index 08ba8c665d0..738f23ef0ea 100644 --- a/estate/models/inhereted_users.py +++ b/estate/models/inhereted_users.py @@ -1,10 +1,10 @@ from odoo import fields, models + class InheritedModel(models.Model): _inherit = "res.users" property_ids = fields.One2many( - "estate.property", + "estate.property", "salesman_id", domain=[("state", "in", ["new", "offer_received"])]) - diff --git a/estate/tests/__init__.py b/estate/tests/__init__.py new file mode 100644 index 00000000000..dfd37f0be11 --- /dev/null +++ b/estate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_estate diff --git a/estate/tests/test_estate.py b/estate/tests/test_estate.py new file mode 100644 index 00000000000..14b82ca5f82 --- /dev/null +++ b/estate/tests/test_estate.py @@ -0,0 +1,69 @@ +from odoo import Command +from odoo.exceptions import UserError +from odoo.tests import tagged +from odoo.tests.common import TransactionCase +from odoo.tests.form import Form + + +# The CI will run these tests after all the modules are installed, +# not right after installing the one defining it. +@tagged('post_install', '-at_install') +class EstateTestCase(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.buyer_partner = cls.env['res.users'].create({ + 'name': 'some guy', + 'login': 'some guy login', + }) + + sold_property_with_accepted_offer = { + "name": "property1", + "expected_price": "110.0", + "selling_price": "100.0", + "offer_ids": [Command.create({"name": "first offer", "price": "1100.0", "status": "accepted", "partner_id": cls.buyer_partner.partner_id.id})], + } + cls.properties = cls.env['estate.property'].create([sold_property_with_accepted_offer]) + cls.properties.write({"state": "sold"}) + + property_with_no_offer = { + "name": "property2", + "expected_price": "110.0", + "selling_price": "100.0", + "offer_ids": [], + } + cls.env['estate.property'].create([property_with_no_offer]) + + def test_create_offer_on_sold(self): + sold_property_with_accepted_offer = self.properties.search([("name", "=", "property1")]) + + with self.assertRaises(UserError): + self.env["estate.property.offer"].create({ + "name": "invalid offer", + "price": "1200.0", + "property_id": sold_property_with_accepted_offer.id, + "partner_id": self.buyer_partner.partner_id.id, + }) + + def test_no_sell_with_no_offer(self): + property_with_no_offer = self.properties.search([("name", "=", "property2")]) + with self.assertRaises(UserError): + property_with_no_offer.write({ + "state": "sold", + }) + + def test_is_sold_property_marked(self): + sold_property_with_accepted_offer = self.properties.search([("name", "=", "property1")]) + self.assertRecordValues(sold_property_with_accepted_offer, [{"state": "sold"}]) + + property_with_no_offer = self.properties.search([("name", "=", "property2")]) + self.assertRecordValues(property_with_no_offer, [{"state": "new"}]) + + def test_garden_reset(self): + property_with_no_offer = self.properties.search([("name", "=", "property2")]) + self.assertRecordValues(property_with_no_offer, [{"garden_area": 0, "garden_orientation": False, "garden": False}]) + with Form(property_with_no_offer) as property_form: + property_form.garden = True + property = property_form.save() + self.assertRecordValues(property, [{"garden_area": 10, "garden_orientation": "north", "garden": True}]) diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 8d2de3a0537..ff5ddec2581 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -3,13 +3,13 @@ 'depends': [ 'base', 'estate', - 'account' + 'account', ], 'data': [ ], 'views': [ - + ], - 'application':False + 'application': False, } diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index a9661dd609f..6ab5d8ad6bd 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,4 +1,5 @@ -from odoo import models, Command +from odoo import Command, models + class InheritedModel(models.Model): _inherit = "estate.property" @@ -14,14 +15,14 @@ def action_property_sold(self): Command.create({ "name": "six percent charge", "quantity": "1", - "price_unit": record.selling_price * 0.06 + "price_unit": record.selling_price * 0.06, }), Command.create({ "name": "administration fee", "quantity": "1", - "price_unit": 100.0 + "price_unit": 100.0, }), ], - } + }, ) return res From c1476ea99d407e4819b11f1fa4227eced1bcfa55 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 21 Aug 2026 09:33:39 +0200 Subject: [PATCH 15/26] [REF] estate: separated estate views into seperat files In order to improve readability estate views which were all in one file have been refactored such that every view has their own file named according to their id. --- estate/__manifest__.py | 10 +- estate/views/estate_property_actions.xml | 28 ++++++ .../views/estate_property_settings_views.xml | 62 ------------ .../views/estate_property_tag_view_list.xml | 13 +++ .../views/estate_property_type_view_form.xml | 27 ++++++ .../views/estate_property_type_view_list.xml | 14 +++ ...iews.xml => estate_property_view_form.xml} | 95 +------------------ estate/views/estate_property_view_kanban.xml | 39 ++++++++ estate/views/estate_property_view_list.xml | 20 ++++ estate/views/estate_property_view_search.xml | 22 +++++ 10 files changed, 172 insertions(+), 158 deletions(-) create mode 100644 estate/views/estate_property_actions.xml delete mode 100644 estate/views/estate_property_settings_views.xml create mode 100644 estate/views/estate_property_tag_view_list.xml create mode 100644 estate/views/estate_property_type_view_form.xml create mode 100644 estate/views/estate_property_type_view_list.xml rename estate/views/{estate_property_views.xml => estate_property_view_form.xml} (53%) create mode 100644 estate/views/estate_property_view_kanban.xml create mode 100644 estate/views/estate_property_view_list.xml create mode 100644 estate/views/estate_property_view_search.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 606e037153e..3e2e73cc8c2 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,8 +5,14 @@ ], 'data': [ 'data/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_property_settings_views.xml', + 'views/estate_property_actions.xml', + 'views/estate_property_tag_view_list.xml', + 'views/estate_property_type_view_form.xml', + 'views/estate_property_type_view_list.xml', + 'views/estate_property_view_form.xml', + 'views/estate_property_view_kanban.xml', + 'views/estate_property_view_list.xml', + 'views/estate_property_view_search.xml', 'views/users_extra_views.xml', 'data/estate_menus.xml', ], diff --git a/estate/views/estate_property_actions.xml b/estate/views/estate_property_actions.xml new file mode 100644 index 00000000000..a31f2bc8571 --- /dev/null +++ b/estate/views/estate_property_actions.xml @@ -0,0 +1,28 @@ + + + + Property Types + estate.property.type + list,form + + + + Property Tags + estate.property.tag + list,form + + + + Offers + estate.property.offer + list,form + [("property_type_id", "==", active_id)] + + + + Properties + estate.property + list,form,kanban + {'search_default_state': True} + + \ No newline at end of file diff --git a/estate/views/estate_property_settings_views.xml b/estate/views/estate_property_settings_views.xml deleted file mode 100644 index 26dd1d1aa4b..00000000000 --- a/estate/views/estate_property_settings_views.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - - estate.property.type.list - estate.property.type - - - - - - - - - - estate.property.tag.list - estate.property.tag - - - - - - - - - estate.property.type.form - estate.property.type - - -
-
- - - - - - - - - - - - - - -
-
- - - Property Types - estate.property.type - list,form - - - - Property Tags - estate.property.tag - list,form - -
- diff --git a/estate/views/estate_property_tag_view_list.xml b/estate/views/estate_property_tag_view_list.xml new file mode 100644 index 00000000000..3f4b563fcbf --- /dev/null +++ b/estate/views/estate_property_tag_view_list.xml @@ -0,0 +1,13 @@ + + + + estate.property.tag.list + estate.property.tag + + + + + + + + diff --git a/estate/views/estate_property_type_view_form.xml b/estate/views/estate_property_type_view_form.xml new file mode 100644 index 00000000000..89c46e9fdb8 --- /dev/null +++ b/estate/views/estate_property_type_view_form.xml @@ -0,0 +1,27 @@ + + + + estate.property.type.form + estate.property.type + +
+
+
+ + + + + + + + + + + + + +
+
+
+
\ No newline at end of file diff --git a/estate/views/estate_property_type_view_list.xml b/estate/views/estate_property_type_view_list.xml new file mode 100644 index 00000000000..65d009a53a7 --- /dev/null +++ b/estate/views/estate_property_type_view_list.xml @@ -0,0 +1,14 @@ + + + + + estate.property.type.list + estate.property.type + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_view_form.xml similarity index 53% rename from estate/views/estate_property_views.xml rename to estate/views/estate_property_view_form.xml index 4a460a0954c..3808445486f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_view_form.xml @@ -1,87 +1,5 @@ - - - Offers - estate.property.offer - list,form - [("property_type_id", "==", active_id)] - - - - estate.property.search - estate.property - - - - - - - - - - - - - - - - - - estate.property.kanban - estate.property - - - - - - - - -
- Expected price: - -
-
- - Best Offer: - - -
-
- - Selling price: - - -
-
- -
-
-
-
-
-
- - - estate.property.list - estate.property - - - - - - - - - - - - - - estate.property.form estate.property @@ -166,15 +84,4 @@ - - - Properties - estate.property - list,form,kanban - {'search_default_state': True} - - - - -
- + \ No newline at end of file diff --git a/estate/views/estate_property_view_kanban.xml b/estate/views/estate_property_view_kanban.xml new file mode 100644 index 00000000000..2707891db08 --- /dev/null +++ b/estate/views/estate_property_view_kanban.xml @@ -0,0 +1,39 @@ + + + + estate.property.kanban + estate.property + + + + + + + + +
+ Expected price: + +
+
+ + Best Offer: + + +
+
+ + Selling price: + + +
+
+ +
+
+
+
+
+
+
+ diff --git a/estate/views/estate_property_view_list.xml b/estate/views/estate_property_view_list.xml new file mode 100644 index 00000000000..f72fc523a99 --- /dev/null +++ b/estate/views/estate_property_view_list.xml @@ -0,0 +1,20 @@ + + + + estate.property.list + estate.property + + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_view_search.xml b/estate/views/estate_property_view_search.xml new file mode 100644 index 00000000000..af84c967570 --- /dev/null +++ b/estate/views/estate_property_view_search.xml @@ -0,0 +1,22 @@ + + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + \ No newline at end of file From 20f44e23de9869fa570ffa10e34d97c6fe0bf419 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 21 Aug 2026 09:44:53 +0200 Subject: [PATCH 16/26] [FIX] estate: added a field for the property type form so its name can be set. Since the requirements don't allow the property type to be edited directly in the list view a field has been created in the form view to allow setting the property type. --- estate/models/estate_property.py | 2 +- estate/views/estate_property_type_view_form.xml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4ebf83d7cce..08549809ba1 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -81,7 +81,7 @@ def _onchange_garden(self): self.garden_orientation = "north" else: self.garden_area = 0 - self.garden_orientation = "False" + self.garden_orientation = False # ==========button functions============== def action_property_sold(self): diff --git a/estate/views/estate_property_type_view_form.xml b/estate/views/estate_property_type_view_form.xml index 89c46e9fdb8..08bf6eef7d6 100644 --- a/estate/views/estate_property_type_view_form.xml +++ b/estate/views/estate_property_type_view_form.xml @@ -8,6 +8,8 @@
+ + From d5e25b3cefbc6cb8a0fcfc14aa9ba974286084a7 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 21 Aug 2026 14:34:58 +0200 Subject: [PATCH 17/26] [ADD] added buttons and counters added a component containing a button and a counter such that the button increments the counter. also added a component which creates its own children buttons such that it's possible to have a global counter of all the children buttons. --- awesome_owl/static/src/card/card.js | 10 +++++++ awesome_owl/static/src/card/card.xml | 15 ++++++++++ awesome_owl/static/src/counter/counter.js | 20 +++++++++++++ awesome_owl/static/src/counter/counter.xml | 9 ++++++ .../src/global_counter/global_counter.js | 28 +++++++++++++++++++ .../src/global_counter/global_counter.xml | 11 ++++++++ awesome_owl/static/src/playground.js | 15 +++++++++- awesome_owl/static/src/playground.xml | 5 ++-- 8 files changed, 110 insertions(+), 3 deletions(-) create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml create mode 100644 awesome_owl/static/src/global_counter/global_counter.js create mode 100644 awesome_owl/static/src/global_counter/global_counter.xml diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..3fe41f5c426 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,10 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + + static props = { + title : String, + content : String + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..bdd09e17ed1 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,15 @@ + + + +
+
+
+ +
+

+ +

+
+
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..5591d195618 --- /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.state = useState({ value: 0 }); + } + + increment() { + if (this.props.onChange){ + this.props.onChange(); + } + this.state.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..56e10866159 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + +
+

Counter:

+ +
+
+
diff --git a/awesome_owl/static/src/global_counter/global_counter.js b/awesome_owl/static/src/global_counter/global_counter.js new file mode 100644 index 00000000000..d7f5d8acfd7 --- /dev/null +++ b/awesome_owl/static/src/global_counter/global_counter.js @@ -0,0 +1,28 @@ +import { Component, useState } from "@odoo/owl"; +import { Counter } from "../counter/counter"; + +export class GlobalCounter extends Component { + static template = "awesome_owl.global_counter"; + + static components = { + Counter + } + + static props = { + buttons: Number + } + + setup() { + this.buttons = [] + for (let i = 0; i < this.props.buttons; i++) { + this.buttons.push(i) + } + + this.state = useState({ value: 2 }); + this.incrementSum = this.incrementSum.bind(this) + } + + incrementSum() { + this.state.value++; + } +} diff --git a/awesome_owl/static/src/global_counter/global_counter.xml b/awesome_owl/static/src/global_counter/global_counter.xml new file mode 100644 index 00000000000..5e3df1ebcef --- /dev/null +++ b/awesome_owl/static/src/global_counter/global_counter.xml @@ -0,0 +1,11 @@ + + + +
+

the sum is:

+ + + +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..ce8fa0724e4 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,18 @@ -import { Component } from "@odoo/owl"; +import { Component, useState, markup } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { GlobalCounter } from "./global_counter/global_counter"; export class Playground extends Component { + setup(){ + this.normal_string = "normal string" + this.html_string = markup("Visit W3Schools.com!") + } + static template = "awesome_owl.playground"; + static components = { + Counter, + Card, + GlobalCounter + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..1f9ba823bc6 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,11 @@ -
hello world
+ + +
-
From ef40a4d4ee82d334f987a0c41ea2f002e4093ba3 Mon Sep 17 00:00:00 2001 From: Kevin Date: Mon, 24 Aug 2026 15:50:09 +0200 Subject: [PATCH 18/26] [ADD] finished chapter one of web framework --- awesome_owl/static/src/card/card.js | 11 +++- awesome_owl/static/src/card/card.xml | 11 +++- awesome_owl/static/src/playground.js | 6 +- awesome_owl/static/src/playground.xml | 11 +++- awesome_owl/static/src/todolist/todo_item.js | 24 ++++++++ awesome_owl/static/src/todolist/todo_item.xml | 12 ++++ awesome_owl/static/src/todolist/todo_list.js | 56 +++++++++++++++++++ awesome_owl/static/src/todolist/todo_list.xml | 14 +++++ 8 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 awesome_owl/static/src/todolist/todo_item.js create mode 100644 awesome_owl/static/src/todolist/todo_item.xml create mode 100644 awesome_owl/static/src/todolist/todo_list.js create mode 100644 awesome_owl/static/src/todolist/todo_list.xml diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 3fe41f5c426..92d207206ab 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -5,6 +5,15 @@ export class Card extends Component { static props = { title : String, - content : String + slots : { type: Object, optional: true} + } + + setup() { + this.state = useState({ open: false }); + this.changeState = this.changeState.bind(this) + } + + changeState() { + this.state.open = !this.state.open } } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index bdd09e17ed1..95d3f84033a 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -6,9 +6,14 @@
-

- -

+ + + + +
+ + Number of new orders this month +
+ +
+
+ + Total amount of new orders this month +
+ +
+
+ + Average amount of t-shirt by order this month +
+ +
+
+ + Number of cancelled orders this month +
+ +
+
+ + Average time for an order to go from 'new' to 'sent' or 'cancelled' +
+ +
+
+
+
+ diff --git a/awesome_dashboard/static/src/dashboardItem/dashboard_item.js b/awesome_dashboard/static/src/dashboardItem/dashboard_item.js new file mode 100644 index 00000000000..2aee1cc02e0 --- /dev/null +++ b/awesome_dashboard/static/src/dashboardItem/dashboard_item.js @@ -0,0 +1,10 @@ +import { Component, useState } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.dashboarditem"; + + static props = { + size: { type: Number, optional: true, default: 1 }, + slots: { type: Object, optional: true} + } +} diff --git a/awesome_dashboard/static/src/dashboardItem/dashboard_item.xml b/awesome_dashboard/static/src/dashboardItem/dashboard_item.xml new file mode 100644 index 00000000000..55073fa8335 --- /dev/null +++ b/awesome_dashboard/static/src/dashboardItem/dashboard_item.xml @@ -0,0 +1,12 @@ + + + + +
+
+ +
+
+
+ +
From 502b3c8d9acc9c0642b0debd18eb779d34cb9776 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 26 Aug 2026 09:08:12 +0200 Subject: [PATCH 22/26] [ADD] [Tutorial] Restrict access to data started --- estate/__manifest__.py | 2 ++ estate/data/ir.model.access.csv | 16 ++++++++++---- estate/data/security.xml | 38 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 estate/data/security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3e2e73cc8c2..b3cc5efd590 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,6 +4,7 @@ 'base', ], 'data': [ + 'data/security.xml', 'data/ir.model.access.csv', 'views/estate_property_actions.xml', 'views/estate_property_tag_view_list.xml', @@ -17,4 +18,5 @@ 'data/estate_menus.xml', ], 'application': True, + 'category': 'Real Estate/Brokerage', } diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv index a6a34c6144d..bc273d0702a 100644 --- a/estate/data/ir.model.access.csv +++ b/estate/data/ir.model.access.csv @@ -1,6 +1,14 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1 -access_estate_property_type_user,access_estate_property_user,model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag_user,access_estate_property_user,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer_user,access_estate_property_user,model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_manager,estate.property.manager,model_estate_property,estate_group_manager,1,1,1,0 +access_estate_property_type_manager,estate.property.type.manager,model_estate_property_type,estate_group_manager,1,1,1,1 +access_estate_property_tag_manager,estate.property.tag.manager,model_estate_property_tag,estate_group_manager,1,1,1,1 +access_estate_property_offer_manager,estate.property.offer.manager,model_estate_property_offer,estate_group_manager,1,1,1,1 +access_estate_property_agent,estate.property.agent,model_estate_property,estate_group_user,1,1,1,0 +access_estate_property_type_agent,estate.property.type.agent,model_estate_property_type,estate_group_user,1,0,0,0 +access_estate_property_tag_agent,estate.property.tag.agent,model_estate_property_tag,estate_group_user,1,0,0,0 +access_estate_property_offer_agent,estate.property.offer.agent,model_estate_property_offer,estate_group_user,1,1,1,0 +access_estate_property_admin,estate.property.admin,model_estate_property,base.group_system,1,1,1,1 +access_estate_property_type_admin,estate.property.type.admin,model_estate_property_type,base.group_system,1,1,1,1 +access_estate_property_tag_admin,estate.property.tag.admin,model_estate_property_tag,base.group_system,1,1,1,1 +access_estate_property_offer_admin,estate.property.offer.adin,model_estate_property_offer,base.group_system,1,1,1,1 \ No newline at end of file diff --git a/estate/data/security.xml b/estate/data/security.xml new file mode 100644 index 00000000000..286d5e61048 --- /dev/null +++ b/estate/data/security.xml @@ -0,0 +1,38 @@ + + + + + Real Estate + + + + Agent + + + + + Manager + + + + + Agents can only see unassigned or own properties + + ['|', ('salesman_id', '=', user.partner_id.id), ('salesman_id', '=', False)] + + + + + + + + Managers can see all properties + + [(1, '=', 1)] + + + + + + + \ No newline at end of file From 6d5c63e105b2707b4fefb065b662661f2bec35a6 Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 26 Aug 2026 11:57:05 +0200 Subject: [PATCH 23/26] [ADD] web framework tutorial chapter 2, part 5, added caching --- .../src/cachingService/caching_service.js | 22 +++++++++++++++++++ awesome_dashboard/static/src/dashboard.js | 9 ++++---- 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 awesome_dashboard/static/src/cachingService/caching_service.js diff --git a/awesome_dashboard/static/src/cachingService/caching_service.js b/awesome_dashboard/static/src/cachingService/caching_service.js new file mode 100644 index 00000000000..13912933dd9 --- /dev/null +++ b/awesome_dashboard/static/src/cachingService/caching_service.js @@ -0,0 +1,22 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { memoize } from "@web/core/utils/functions" + + +export async function _loadStatistics() { + const result = await rpc("/awesome_dashboard/statistics") + return result +} + +export const myCaching = { + start(env) { + const memoLoadStatistics = memoize(_loadStatistics) + return { + loadStatistics() { + return memoLoadStatistics() + } + } + }, + } + +registry.category("services").add("myCaching", myCaching); \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index a4d82b35274..0ced121d69a 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -15,17 +15,16 @@ class AwesomeDashboard extends Component { setup() { this.action = useService("action") + this.caching = useService("myCaching") 9 this.state = useState({ statistics: {} }) onWillStart(async () => { - const result = await rpc( - "/awesome_dashboard/statistics", - ) - console.log(result) - this.state.statistics = result + const stats = await this.caching.loadStatistics() + this.state.statistics = stats + }) } From 356cd8ed0f44fd4ce9c007da2ffc53f93cac793d Mon Sep 17 00:00:00 2001 From: Kevin Date: Wed, 26 Aug 2026 13:44:24 +0200 Subject: [PATCH 24/26] [ADD] web framework tutorial chapter 2, part 7 added chart to dashboard --- awesome_dashboard/static/src/dashboard.js | 4 +- awesome_dashboard/static/src/dashboard.xml | 3 ++ .../static/src/piechart/piechart.js | 43 +++++++++++++++++++ .../static/src/piechart/piechart.xml | 12 ++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 awesome_dashboard/static/src/piechart/piechart.js create mode 100644 awesome_dashboard/static/src/piechart/piechart.xml diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 0ced121d69a..6b387e7687f 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -4,13 +4,15 @@ import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; import { DashboardItem } from "./dashboardItem/dashboard_item"; import { rpc } from "@web/core/network/rpc"; +import { Piechart } from "./piechart/piechart"; class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; static components = { DashboardItem, - Layout + Layout, + Piechart } setup() { diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index d616b4e81af..d17885db391 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -40,6 +40,9 @@ + + + diff --git a/awesome_dashboard/static/src/piechart/piechart.js b/awesome_dashboard/static/src/piechart/piechart.js new file mode 100644 index 00000000000..cce045d2bcb --- /dev/null +++ b/awesome_dashboard/static/src/piechart/piechart.js @@ -0,0 +1,43 @@ +import { loadJS } from "@web/core/assets" +import { Component, useState, onWillStart, onMounted, useRef } from "@odoo/owl"; + +export class Piechart extends Component { + static template = "awesome_dashboard.piechart"; + + static props = { + pieData: Object + } + + setup() { + this.chartRef = useRef("chart") + + this.state = useState({}) + + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js") + + }) + onMounted(async () => { + const ordersBySize = this.props.pieData.orders_by_size + const data = { + labels: ["m", "s", "xl"], + datasets: [{ + label: "tshirt orders by size", + data: [ordersBySize.m, ordersBySize.s, ordersBySize.xl], + backgroundColor: [ + 'rgb(255, 99, 132)', + 'rgb(54, 162, 235)', + 'rgb(255, 205, 86)' + ], + }], + } + const config = { + type: 'pie', + data: data, + }; + + new Chart(this.chartRef.el, config) + }) + + } +} diff --git a/awesome_dashboard/static/src/piechart/piechart.xml b/awesome_dashboard/static/src/piechart/piechart.xml new file mode 100644 index 00000000000..e84fd8f4fad --- /dev/null +++ b/awesome_dashboard/static/src/piechart/piechart.xml @@ -0,0 +1,12 @@ + + + + +
+ + + +
+
+ +
\ No newline at end of file From fdf84cf26a316e2164b4a3fd18a45000b0bfecc7 Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 27 Aug 2026 10:11:42 +0200 Subject: [PATCH 25/26] [ADD] discover the web framework chapter 2 part 9 made the dashboard generic --- awesome_dashboard/__manifest__.py | 6 +- awesome_dashboard/static/src/dashboard.xml | 51 -------------- .../cachingService/caching_service.js | 8 ++- .../static/src/{ => dashboard}/dashboard.js | 22 +++--- .../static/src/{ => dashboard}/dashboard.scss | 0 .../static/src/dashboard/dashboard.xml | 25 +++++++ .../dashboardItem/dashboard_item.js | 0 .../dashboardItem/dashboard_item.xml | 0 .../static/src/dashboard/dashboard_items.js | 69 +++++++++++++++++++ .../src/dashboard/numberCard/number_card.js | 11 +++ .../src/dashboard/numberCard/number_card.xml | 7 ++ .../dashboard/pieChartCard/piechart_card.js | 13 ++++ .../dashboard/pieChartCard/piechart_card.xml | 7 ++ .../src/{ => dashboard}/piechart/piechart.js | 20 ++++-- .../src/{ => dashboard}/piechart/piechart.xml | 0 .../static/src/dashboard_action.js | 10 +++ .../static/src/dashboard_action.xml | 9 +++ 17 files changed, 190 insertions(+), 68 deletions(-) delete mode 100644 awesome_dashboard/static/src/dashboard.xml rename awesome_dashboard/static/src/{ => dashboard}/cachingService/caching_service.js (64%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.js (63%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard.scss (100%) create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml rename awesome_dashboard/static/src/{ => dashboard}/dashboardItem/dashboard_item.js (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboardItem/dashboard_item.xml (100%) create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js create mode 100644 awesome_dashboard/static/src/dashboard/numberCard/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/numberCard/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.js create mode 100644 awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.xml rename awesome_dashboard/static/src/{ => dashboard}/piechart/piechart.js (58%) rename awesome_dashboard/static/src/{ => dashboard}/piechart/piechart.xml (100%) create mode 100644 awesome_dashboard/static/src/dashboard_action.js create mode 100644 awesome_dashboard/static/src/dashboard_action.xml diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..89481aa5f4a 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -24,7 +24,11 @@ 'assets': { 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', + ('remove', 'awesome_dashboard/static/src/dashboard/**/*'), + ], + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*', ], }, - 'license': 'AGPL-3' + 'license': 'AGPL-3', } diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index d17885db391..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - -
- - Number of new orders this month -
- -
-
- - Total amount of new orders this month -
- -
-
- - Average amount of t-shirt by order this month -
- -
-
- - Number of cancelled orders this month -
- -
-
- - Average time for an order to go from 'new' to 'sent' or 'cancelled' -
- -
-
- - - -
-
-
- - -
diff --git a/awesome_dashboard/static/src/cachingService/caching_service.js b/awesome_dashboard/static/src/dashboard/cachingService/caching_service.js similarity index 64% rename from awesome_dashboard/static/src/cachingService/caching_service.js rename to awesome_dashboard/static/src/dashboard/cachingService/caching_service.js index 13912933dd9..d36ab2138ef 100644 --- a/awesome_dashboard/static/src/cachingService/caching_service.js +++ b/awesome_dashboard/static/src/dashboard/cachingService/caching_service.js @@ -1,19 +1,21 @@ -import { registry } from "@web/core/registry"; +import { registry, reactive } from "@web/core/registry"; import { rpc } from "@web/core/network/rpc"; import { memoize } from "@web/core/utils/functions" export async function _loadStatistics() { + // console.log("loading statistics") const result = await rpc("/awesome_dashboard/statistics") + // console.log(result) return result } export const myCaching = { start(env) { - const memoLoadStatistics = memoize(_loadStatistics) return { loadStatistics() { - return memoLoadStatistics() + let memoLoadStatistics = memoize(_loadStatistics) + return memoLoadStatistics } } }, diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js similarity index 63% rename from awesome_dashboard/static/src/dashboard.js rename to awesome_dashboard/static/src/dashboard/dashboard.js index 6b387e7687f..9bc760486cc 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -1,4 +1,4 @@ -import { Component, onWillStart, useState } from "@odoo/owl"; +import { Component, onWillStart, useState, onMounted } from "@odoo/owl"; import { Layout } from "@web/search/layout" import { registry } from "@web/core/registry"; import { useService } from "@web/core/utils/hooks"; @@ -14,19 +14,23 @@ class AwesomeDashboard extends Component { Layout, Piechart } - setup() { this.action = useService("action") this.caching = useService("myCaching") -9 - this.state = useState({ - statistics: {} - }) + this.statistics = useState({}) + this.items = registry.category("awesome_dashboard").get("items"); onWillStart(async () => { const stats = await this.caching.loadStatistics() - this.state.statistics = stats - + Object.assign(this.statistics, await stats()); + }) + onMounted(() => { + setInterval(async () => { + console.log("in de functie") + const stats = await this.caching.loadStatistics() + console.log(await stats()) + Object.assign(this.statistics, await stats()); + }, 5000) }) } @@ -45,4 +49,4 @@ class AwesomeDashboard extends Component { } } -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss similarity index 100% rename from awesome_dashboard/static/src/dashboard.scss rename to awesome_dashboard/static/src/dashboard/dashboard.scss diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..f222977e54e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboardItem/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboardItem/dashboard_item.js similarity index 100% rename from awesome_dashboard/static/src/dashboardItem/dashboard_item.js rename to awesome_dashboard/static/src/dashboard/dashboardItem/dashboard_item.js diff --git a/awesome_dashboard/static/src/dashboardItem/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboardItem/dashboard_item.xml similarity index 100% rename from awesome_dashboard/static/src/dashboardItem/dashboard_item.xml rename to awesome_dashboard/static/src/dashboard/dashboardItem/dashboard_item.xml 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..715e67199ae --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,69 @@ +import { NumberCard } from "./numberCard/number_card"; +import { PieChartCard } from "./pieChartCard/piechart_card"; +import { registry } from "@web/core/registry"; + + +const items = [ + { + id: "average_quantity", + description: "Average amount of t-shirt", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity + }) + }, + { + id: "average_time", + description: "Average delivery time", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Average time it takes to deliver a t-shirt", + value: data.average_time + }) + }, + { + id: "nb_cancelled_orders", + description: "Number of cancelled orders", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Number of orders that were cancelled.", + value: data.nb_cancelled_orders + }) + }, + { + id: "nb_new_orders", + description: "Number of new orders", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Number of orders that were new.", + value: data.nb_new_orders + }) + }, + { + id: "total_amount", + description: "Total amount of orders", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Total number of orders", + value: data.total_amount + }) + }, + { + id: "pie_chart", + description: "Orders by size", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "Display the orders in a pie chart by size", + value: data.orders_by_size + }) + }, +] + +registry.category("awesome_dashboard").add("items", items); diff --git a/awesome_dashboard/static/src/dashboard/numberCard/number_card.js b/awesome_dashboard/static/src/dashboard/numberCard/number_card.js new file mode 100644 index 00000000000..280d9b894e2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/numberCard/number_card.js @@ -0,0 +1,11 @@ +import { loadJS } from "@web/core/assets" +import { Component, useState, onWillStart, onMounted, useRef, reactive, onWillUnmount, onWillPatch, onWillUpdateProps } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.numberCard"; + + static props = { + title: String, + value: Number + } +} diff --git a/awesome_dashboard/static/src/dashboard/numberCard/number_card.xml b/awesome_dashboard/static/src/dashboard/numberCard/number_card.xml new file mode 100644 index 00000000000..6e753dba1e4 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/numberCard/number_card.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.js b/awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.js new file mode 100644 index 00000000000..c66ce310f65 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.js @@ -0,0 +1,13 @@ +import { loadJS } from "@web/core/assets" +import { Component, useState, onWillStart, onMounted, useRef, reactive, onWillUnmount, onWillPatch, onWillUpdateProps } from "@odoo/owl"; +import { Piechart } from "../piechart/piechart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.pieChartCard"; + static components = { Piechart } + + static props = { + title: String, + value: Object + } +} diff --git a/awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.xml b/awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.xml new file mode 100644 index 00000000000..df5a4168b3e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pieChartCard/piechart_card.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/awesome_dashboard/static/src/piechart/piechart.js b/awesome_dashboard/static/src/dashboard/piechart/piechart.js similarity index 58% rename from awesome_dashboard/static/src/piechart/piechart.js rename to awesome_dashboard/static/src/dashboard/piechart/piechart.js index cce045d2bcb..861b1f615dc 100644 --- a/awesome_dashboard/static/src/piechart/piechart.js +++ b/awesome_dashboard/static/src/dashboard/piechart/piechart.js @@ -1,5 +1,5 @@ import { loadJS } from "@web/core/assets" -import { Component, useState, onWillStart, onMounted, useRef } from "@odoo/owl"; +import { Component, useState, onWillStart, onMounted, useRef, reactive, onWillUnmount, onWillPatch, onWillUpdateProps } from "@odoo/owl"; export class Piechart extends Component { static template = "awesome_dashboard.piechart"; @@ -15,10 +15,10 @@ export class Piechart extends Component { onWillStart(async () => { await loadJS("/web/static/lib/Chart/Chart.js") - }) onMounted(async () => { - const ordersBySize = this.props.pieData.orders_by_size + // chart stuff + const ordersBySize = this.props.pieData const data = { labels: ["m", "s", "xl"], datasets: [{ @@ -36,8 +36,20 @@ export class Piechart extends Component { data: data, }; - new Chart(this.chartRef.el, config) + this.chart = new Chart(this.chartRef.el, config) }) + onWillUpdateProps((nextProps) => { + const ordersBySize = nextProps.pieData + if (this.chart && ordersBySize) { + this.chart.data.datasets[0].data = [ + ordersBySize.m || 0, + ordersBySize.s || 0, + ordersBySize.xl || 0 + ]; + + this.chart.update(); + } + }); } } diff --git a/awesome_dashboard/static/src/piechart/piechart.xml b/awesome_dashboard/static/src/dashboard/piechart/piechart.xml similarity index 100% rename from awesome_dashboard/static/src/piechart/piechart.xml rename to awesome_dashboard/static/src/dashboard/piechart/piechart.xml diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..5b57fd27a1d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,10 @@ +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; +import { Component, xml } from "@odoo/owl"; + +class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = "awesome_dashboard.awesomedashboardloader" +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard_action.xml b/awesome_dashboard/static/src/dashboard_action.xml new file mode 100644 index 00000000000..11fee2a0f0f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.xml @@ -0,0 +1,9 @@ + + + + + + + + + From a22a4ec0d6baf402ed9001c44b1c708c1c12af3b Mon Sep 17 00:00:00 2001 From: Kevin Date: Thu, 27 Aug 2026 16:28:06 +0200 Subject: [PATCH 26/26] [ADD] web framework chapter 2 finished making the dashoard customizable --- .../static/src/dashboard/dashboard.js | 26 +++++++++++-- .../static/src/dashboard/dashboard.xml | 17 +++++---- .../static/src/dashboard/mydialog/mydialog.js | 37 +++++++++++++++++++ .../src/dashboard/mydialog/mydialog.xml | 21 +++++++++++ 4 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard/mydialog/mydialog.js create mode 100644 awesome_dashboard/static/src/dashboard/mydialog/mydialog.xml diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js index 9bc760486cc..12a7b388882 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -5,6 +5,9 @@ import { useService } from "@web/core/utils/hooks"; import { DashboardItem } from "./dashboardItem/dashboard_item"; import { rpc } from "@web/core/network/rpc"; import { Piechart } from "./piechart/piechart"; +import { MyDialog } from "./mydialog/mydialog"; +import { browser } from "@web/core/browser/browser"; + class AwesomeDashboard extends Component { static template = "awesome_dashboard.AwesomeDashboard"; @@ -12,13 +15,18 @@ class AwesomeDashboard extends Component { static components = { DashboardItem, Layout, - Piechart + Piechart, + MyDialog } setup() { this.action = useService("action") this.caching = useService("myCaching") this.statistics = useState({}) this.items = registry.category("awesome_dashboard").get("items"); + this.dialog = useService("dialog") + this.state = useState({ + disabledItems: JSON.parse(browser.localStorage.getItem("disabledDashboardItems")?.split(",") || "{}") + }) onWillStart(async () => { const stats = await this.caching.loadStatistics() @@ -26,14 +34,26 @@ class AwesomeDashboard extends Component { }) onMounted(() => { setInterval(async () => { - console.log("in de functie") const stats = await this.caching.loadStatistics() - console.log(await stats()) Object.assign(this.statistics, await stats()); }, 5000) }) } + _updateConfiguration(newDisabledItems) { + this.state.disabledItems = newDisabledItems + console.log(this.state.disabledItems.average_quantity) + console.log(this.items[0]) + } + + openConfiguration() { + this.dialog.add(MyDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + updateConfiguration: this._updateConfiguration.bind(this), + }) + } + kanban_action() { this.action.doAction("base.action_partner_form") } diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml index f222977e54e..f82bfd76952 100644 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -8,15 +8,18 @@ + - - - + - - - - + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/mydialog/mydialog.js b/awesome_dashboard/static/src/dashboard/mydialog/mydialog.js new file mode 100644 index 00000000000..958cfae6799 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/mydialog/mydialog.js @@ -0,0 +1,37 @@ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; + + +export class MyDialog extends Component { + static template = "awesome_dashboard.mydialog"; + + static components = { + Dialog, + CheckBox + } + + static props = { + disabledItems: Object, + updateConfiguration: Function, + items: Array, + close: Function + } + + setup() { + console.log(this.props) + } + + onChange(ev, item) { + // this.disabledItems[item.id] = ev + this.props.disabledItems[item.id] = ev + } + + done() { + browser.localStorage.setItem("disabledDashboardItems", JSON.stringify(this.props.disabledItems)) + this.props.updateConfiguration(this.props.disabledItems) + this.props.close() + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/mydialog/mydialog.xml b/awesome_dashboard/static/src/dashboard/mydialog/mydialog.xml new file mode 100644 index 00000000000..a5de80547ca --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/mydialog/mydialog.xml @@ -0,0 +1,21 @@ + + + + + + +
+ + + +
+
+ + + +
+
+ +