From 2b35be3e65a7794b711a005ef83966664d2e296e Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 17 Feb 2025 11:53:48 +0100 Subject: [PATCH 01/27] [IMP] tutorials: first commit modules: tutorials/awesome_clicker files: __init__.py first commit following the instructions of the tutorials --- awesome_clicker/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/awesome_clicker/__init__.py b/awesome_clicker/__init__.py index 40a96afc6ff..4d806117b86 100644 --- a/awesome_clicker/__init__.py +++ b/awesome_clicker/__init__.py @@ -1 +1,5 @@ # -*- coding: utf-8 -*- + +#TEST COMMIT + +print("test") From 54226320dfb6ab9b57447605f422e06b6812acff Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 17 Feb 2025 14:48:53 +0100 Subject: [PATCH 02/27] [ADD] estate: creation of Real Estate App (Chapter 2) --- estate/__init__.py | 0 estate/__manifest__.py | 10 ++++++++++ 2 files changed, 10 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..d8d0021bb87 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Real Estate', + 'depends': [ + 'base', + ], + 'application': True, +} From 4a606cae9ea697fef8298c5ef7ee587935743679 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 17 Feb 2025 15:49:06 +0100 Subject: [PATCH 03/27] [IMP] estate: model creation (Chapter 3) --- estate/__init__.py | 4 ++++ estate/models/__init__.py | 4 ++++ estate/models/estate_property.py | 24 ++++++++++++++++++++++++ 3 files changed, 32 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..dc5e6b693d1 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..ddcab4c3593 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..0a216ab6a47 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + +class EstateProperty(models.Model): + _name = "estate_property" + _description = "Real Estate" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] + ) From 201e16d2b0e957da8411ceaf7f7528d165f0b101 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 17 Feb 2025 16:25:31 +0100 Subject: [PATCH 04/27] [IMP] estate: security rules (Chapter 4) --- estate/__manifest__.py | 4 ++++ estate/security/ir.model.access.csv | 2 ++ 2 files changed, 6 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d8d0021bb87..e50c614867e 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,5 +6,9 @@ 'depends': [ 'base', ], + 'data': [ + 'security/ir.model.access.csv', + ], 'application': True, + 'license': 'LGPL-3' } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..4bda9a1176e --- /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_model,access_estate_property_model,model_estate_property,base.group_user,1,1,1,1 From 958d6d54736edfe995bb4d4fb70113a7e024ccbe Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 17 Feb 2025 18:10:04 +0100 Subject: [PATCH 05/27] [IMP] estate: first user interfaces (Chapter 5) --- estate/__manifest__.py | 3 +++ estate/models/estate_property.py | 18 +++++++++++++----- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 7 +++++++ 4 files changed, 30 insertions(+), 5 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 e50c614867e..52b9fc29abe 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,9 @@ ], 'data': [ 'security/ir.model.access.csv', + + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], 'application': True, 'license': 'LGPL-3' diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0a216ab6a47..624056df207 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,19 +1,19 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - +from dateutil.relativedelta import relativedelta from odoo import fields, models class EstateProperty(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "Real Estate" name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(default=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() @@ -22,3 +22,11 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] ) + active = fields.Boolean(default=True) + state = fields.Selection( + selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), ('cancelled', 'Cancelled')], + copy=False, + default='new', + required=True + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..8f7c56dc1dc --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..4d544c18597 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate.property + list,form + + From a20009a719a9fa4e95ef42b5f4e12aa51cecb33a Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Tue, 18 Feb 2025 11:38:42 +0100 Subject: [PATCH 06/27] [IMP] estate: custom user interfaces (Chapter 6) + first review fixes --- estate/__init__.py | 3 -- estate/__manifest__.py | 3 -- estate/models/__init__.py | 3 -- estate/models/estate_property.py | 13 +++-- estate/views/estate_menus.xml | 6 +-- estate/views/estate_property_views.xml | 75 +++++++++++++++++++++++++- 6 files changed, 85 insertions(+), 18 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index dc5e6b693d1..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,4 +1 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 52b9fc29abe..a884dd387b3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - { 'name': 'Real Estate', 'depends': [ diff --git a/estate/models/__init__.py b/estate/models/__init__.py index ddcab4c3593..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 624056df207..673acb88307 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,8 +1,7 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. from dateutil.relativedelta import relativedelta from odoo import fields, models + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate" @@ -24,9 +23,13 @@ class EstateProperty(models.Model): ) active = fields.Boolean(default=True) state = fields.Selection( - 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'), + ], copy=False, default='new', - required=True + required=True, ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 8f7c56dc1dc..2d1d92f864b 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,7 @@ - - - + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4d544c18597..531ce39e6d2 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,78 @@ - + + estate.property.form + estate.property + +
+ + + +

+ +

+
+ + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.view.search + estate.property + + + + + + + + + + + + + + + Properties estate.property list,form From f61efe40dfd8f075d26ce95064dfe07904c7b39f Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Tue, 18 Feb 2025 16:17:05 +0100 Subject: [PATCH 07/27] [IMP] estate: relation between models (Chapter 7) --- estate/__manifest__.py | 4 ++- estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 17 +++++++++ estate/models/estate_property_offer.py | 18 ++++++++++ estate/models/estate_property_tag.py | 8 +++++ estate/models/estate_property_type.py | 8 +++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 4 +++ estate/views/estate_property_offer_views.xml | 29 +++++++++++++++ estate/views/estate_property_type_views.xml | 15 ++++++++ estate/views/estate_property_views.xml | 37 +++++++++++++++++--- 11 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a884dd387b3..a9f001b9796 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,9 +6,11 @@ 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_offer_views.xml', + 'views/estate_property_type_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', ], 'application': True, - 'license': 'LGPL-3' + 'license': 'LGPL-3', } 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 673acb88307..673e1f4cb89 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -33,3 +33,20 @@ class EstateProperty(models.Model): default='new', required=True, ) + property_type_id = fields.Many2one( + 'estate.property.type', string='Property Type', + ) + partner_id = fields.Many2one( + 'res.partner', string='Buyer', + ) + user_id = fields.Many2one( + 'res.users', string='Salesman', + default=lambda self: self.env.user, + copy=False, + ) + tag_ids = fields.Many2many( + 'estate.property.tag', + ) + 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..75bc3227341 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,18 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + price = fields.Float() + status = fields.Selection( + selection=[('accepted', 'Accepted'), ('refused', 'Refused')], + copy=False, + ) + partner_id = fields.Many2one( + 'res.partner', required=True + ) + property_id = fields.Many2one( + 'estate.property', required=True + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..4c8a6e62e98 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real Estate 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..ae980958fc6 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real Estate Type" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4bda9a1176e..4073dc03078 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink access_estate_property_model,access_estate_property_model,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_model,access_estate_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_model,access_estate_property_offer_model,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 2d1d92f864b..2e13d034d59 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -3,5 +3,9 @@ + + + +
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..c1e10864ddc --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,29 @@ + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+ + + estate.property.offer.list + estate.property.offer + + + + + + + + +
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..514cc74c223 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,15 @@ + + + estate.property.type.form + estate.property.type + +
+ +

+ +

+
+
+
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 531ce39e6d2..50173d03661 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,7 +3,7 @@ estate.property.form estate.property -
+ @@ -11,13 +11,17 @@ + + + - - + + + - - + + @@ -33,6 +37,17 @@ + + + + + + + + + + +
@@ -77,4 +92,16 @@ estate.property list,form + + + Property Types + estate.property.type + list,form + + + + Property Tags + estate.property.tag + list,form + From 39313920c7e26cba98245688624bddd40f05cb51 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Wed, 19 Feb 2025 09:48:00 +0100 Subject: [PATCH 08/27] [IMP] estate: computed fields and onchanges (Chapter 8) --- awesome_clicker/__init__.py | 4 -- estate/models/estate_property.py | 42 +++++++++++++------- estate/models/estate_property_offer.py | 29 +++++++++++--- estate/views/estate_property_offer_views.xml | 4 ++ estate/views/estate_property_views.xml | 4 +- 5 files changed, 58 insertions(+), 25 deletions(-) diff --git a/awesome_clicker/__init__.py b/awesome_clicker/__init__.py index 4d806117b86..40a96afc6ff 100644 --- a/awesome_clicker/__init__.py +++ b/awesome_clicker/__init__.py @@ -1,5 +1 @@ # -*- coding: utf-8 -*- - -#TEST COMMIT - -print("test") diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 673e1f4cb89..911ca9bc4c5 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 fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -27,26 +27,40 @@ class EstateProperty(models.Model): ('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), ('cancelled', 'Cancelled'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), ], copy=False, default='new', required=True, ) - property_type_id = fields.Many2one( - 'estate.property.type', string='Property Type', - ) - partner_id = fields.Many2one( - 'res.partner', string='Buyer', - ) + property_type_id = fields.Many2one('estate.property.type', string='Property Type') + partner_id = fields.Many2one('res.partner', string='Buyer') user_id = fields.Many2one( 'res.users', string='Salesman', default=lambda self: self.env.user, copy=False, ) - tag_ids = fields.Many2many( - 'estate.property.tag', - ) - offer_ids = fields.One2many( - 'estate.property.offer', 'property_id', string='Offers' - ) + tag_ids = fields.Many2many('estate.property.tag') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + total_area = fields.Integer(compute="_compute_total_area") + best_price = fields.Float(compute="_compute_best_price") + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for property in self: + property.total_area = property.living_area + property.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for property in self: + property.best_price = max(property.offer_ids.mapped('price')) if len(property.offer_ids) > 0 else 0.0 + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = '' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 75bc3227341..08960eb2084 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from datetime import datetime +from dateutil.relativedelta import relativedelta +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -10,9 +12,24 @@ class EstatePropertyOffer(models.Model): selection=[('accepted', 'Accepted'), ('refused', 'Refused')], copy=False, ) - partner_id = fields.Many2one( - 'res.partner', required=True - ) - property_id = fields.Many2one( - 'estate.property', required=True + validity = fields.Integer(default=7, string="Validity (days)") + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate.property', required=True) + date_deadline = fields.Date( + compute='_compute_date_deadline', + inverse='_inverse_date_deadline', + string="Deadline", ) + + @api.depends('validity', 'create_date') + def _compute_date_deadline(self): + for offer in self: + offer.date_deadline = ( + offer.create_date + relativedelta(days=offer.validity) + if type(offer.create_date) is datetime + else fields.Date.context_today(self) + relativedelta(days=offer.validity) + ) + + def _inverse_date_deadline(self): + for offer in self: + offer.validity = (offer.date_deadline - offer.create_date.date()).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index c1e10864ddc..e4a2c8a91ae 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + @@ -22,6 +24,8 @@ + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 50173d03661..508655f6e50 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -21,6 +21,7 @@ + @@ -35,6 +36,7 @@ + @@ -81,7 +83,7 @@ - + From 400e59ea0281a95fc162710cd6f0c0d7663c5ba3 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Wed, 19 Feb 2025 11:03:12 +0100 Subject: [PATCH 09/27] [IMP] estate: Action Buttons (Chapter 9) --- estate/models/estate_property.py | 18 ++++++++++++++++++ estate/models/estate_property_offer.py | 12 ++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 5 +++++ 4 files changed, 37 insertions(+) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 911ca9bc4c5..d2930a13210 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 api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -33,6 +34,7 @@ class EstateProperty(models.Model): copy=False, default='new', required=True, + string='Status', ) property_type_id = fields.Many2one('estate.property.type', string='Property Type') partner_id = fields.Many2one('res.partner', string='Buyer') @@ -64,3 +66,19 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = '' + + def action_set_status_sold(self): + for property in self: + if property.state != 'cancelled': + property.state = 'sold' + else: + raise UserError("Cancelled properties cannot be sold.") + return True + + def action_set_status_cancelled(self): + for property in self: + if property.state != 'sold': + property.state = 'cancelled' + else: + raise UserError("Sold properties cannot be cancelled.") + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 08960eb2084..5f8a333d3c6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from datetime import datetime from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError from odoo import api, fields, models @@ -33,3 +34,14 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for offer in self: offer.validity = (offer.date_deadline - offer.create_date.date()).days + + def action_accept_offer(self): + if not any(offer.status == 'accepted' for offer in self.property_id.offer_ids): + self.status = 'accepted' + self.property_id.partner_id = self.partner_id + self.property_id.selling_price = self.price + else: + raise UserError('An offer is already accepted.') + + def action_refuse_offer(self): + self.status = 'refused' diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index e4a2c8a91ae..2cd18ea0714 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -26,6 +26,8 @@ + + - diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..97233527f62 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,14 @@ + + + res.users.view.form.inherit.estate + res.users + + + + + + + + + + From f7069d93014342a035054f4489b20851d02798fd Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Thu, 20 Feb 2025 16:40:06 +0100 Subject: [PATCH 15/27] [IMP] estate: Link Module (Chapter 13 - part 1) --- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 7 +++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 15 +++++++++++++++ 4 files changed, 24 insertions(+) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..f5ceb4a910c --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,7 @@ +{ + 'name': 'Estate Account', + 'depends': [ + 'estate', 'account', + ], + 'license': 'LGPL-3', +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..7974d4bc939 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,15 @@ +from odoo import models + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_set_status_sold(self): + self.env['account.move'].create( + { + 'partner_id': self.partner_id, + 'move_type': 'out_invoice', + + } + ) + return super().action_set_status_sold() From c3bba6238be72ea4525e295f5cc8812cbc39cea0 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Thu, 20 Feb 2025 16:59:29 +0100 Subject: [PATCH 16/27] [IMP] estate: Link Module (Chapter 13 - part 2) --- estate_account/models/estate_property.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 7974d4bc939..39c30490a34 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models +from odoo import Command, models class EstateProperty(models.Model): @@ -7,9 +7,20 @@ class EstateProperty(models.Model): def action_set_status_sold(self): self.env['account.move'].create( { - 'partner_id': self.partner_id, + 'partner_id': self.partner_id.id, 'move_type': 'out_invoice', - + 'invoice_line_ids': [ + Command.create({ + 'name': '6 Percent of selling price', + 'quantity': 0.06, + 'price_unit': self.selling_price + }), + Command.create({ + 'name': 'Administrative fees', + 'quantity': 1.0, + 'price_unit': 100.0, + }) + ], } ) return super().action_set_status_sold() From 1a23c78553f6a49ce08507be5875ec1b0d94de3a Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Thu, 20 Feb 2025 17:30:36 +0100 Subject: [PATCH 17/27] [IMP] estate: QWeb (Chapter 14 - part 1) --- estate/views/estate_property_views.xml | 28 +++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 52d43038708..2c43d6e1f87 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -83,6 +83,32 @@ + + estate.property.kanban + estate.property + + + + + +
+ +
+ +
+ +
+
+ +
+ +
+
+
+
+
+
+ estate.property.view.search estate.property @@ -103,7 +129,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_available': True, 'search_default_current': True} From 78a306ff9869601eef557e742f7e17633270b558 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Fri, 21 Feb 2025 09:18:15 +0100 Subject: [PATCH 18/27] [IMP] estate: QWeb (Chapter 14 - part 2) --- estate/models/estate_property.py | 8 ++++++++ estate/models/estate_property_type.py | 8 -------- estate/views/estate_property_views.xml | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ae727ed1907..f9194093b2b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -95,3 +95,11 @@ def action_set_status_cancelled(self): property.state = 'cancelled' return True + + @api.ondelete(at_uninstall=False) + def _unlink_except_property_new_cancelled(self): + for property in self: + if not property.state in ('new', 'cancelled'): + raise UserError("Only new and cancelled properties can be deleted.") + + return super().unlink() diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 7abf2e48567..be12336f98a 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -21,11 +21,3 @@ class EstatePropertyType(models.Model): def _compute_offer_count(self): for type in self: type.offer_count = len(type.offer_ids) - - @api.ondelete(at_uninstall=False) - def _unlink_except_property_new_cancelled(self): - for property in self: - if not property.state in ('new', 'cancelled'): - raise UserError("Only new and cancelled properties can be deleted.") - - return super().unlink() diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2c43d6e1f87..53b9fe54154 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -87,7 +87,7 @@ estate.property.kanban estate.property - + @@ -101,7 +101,7 @@
- +
From f7236c19fef248fa50db151e7abbd99807515d62 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Fri, 21 Feb 2025 15:28:50 +0100 Subject: [PATCH 19/27] [IMP] awesome_owl: 1 - 10 --- awesome_owl/static/src/card/card.js | 12 ++++++++ awesome_owl/static/src/card/card.xml | 17 +++++++++++ awesome_owl/static/src/counter/counter.js | 23 +++++++++++++++ awesome_owl/static/src/counter/counter.xml | 9 ++++++ awesome_owl/static/src/playground.js | 23 ++++++++++++++- awesome_owl/static/src/playground.xml | 7 +++++ awesome_owl/static/src/todo/todo_item.js | 11 ++++++++ awesome_owl/static/src/todo/todo_item.xml | 7 +++++ awesome_owl/static/src/todo/todo_list.js | 33 ++++++++++++++++++++++ awesome_owl/static/src/todo/todo_list.xml | 11 ++++++++ awesome_owl/utils.js | 9 ++++++ 11 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml create mode 100644 awesome_owl/static/src/todo/todo_item.js create mode 100644 awesome_owl/static/src/todo/todo_item.xml create mode 100644 awesome_owl/static/src/todo/todo_list.js create mode 100644 awesome_owl/static/src/todo/todo_list.xml create mode 100644 awesome_owl/utils.js diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..686f9aac4be --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,12 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + + static props = { + title: {type: String}, + content: {type: 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..144928be8a3 --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,17 @@ + + + + +
+
+
+ +
+

+ +

+
+
+
+ +
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..c197a3abada --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,23 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + + static props = { + onChange: {type: Function, optional: true} + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + if(this.props.onChange){ + this.props.onChange(); + } + + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..5e1a84ab012 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + + +

Counter

+ +
+ +
\ No newline at end of file diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..8115ed6eec5 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,28 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { markup, Component, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; + + static components = { TodoList, Counter, Card }; + + setup(){ + this.state = useState({ + card1: { title: "card 1", content: "content of card 1" }, + card2: { title: "card 2", content: "content of card 2" }, + sum: 2 + }); + + this.content_value_escaped = "
some content
"; + this.content_value = 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..74bda6a6052 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,6 +4,13 @@
hello world + + +
+ + +

The sum is:

+
diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..de9267d507d --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,11 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component{ + static template = "awesome_owl.todo_item"; + + static props = { + todo: {type: Object} + }; +} diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..6cf82a1472c --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,7 @@ + + + + +
.
+
+
diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..3dcde3ce7ee --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,33 @@ +/** @odoo-module**/ + +import { onMounted, useRef, useState, Component } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutoFocus } from "../../../utils"; + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + + static components = { TodoItem }; + + setup() { + this.todos = useState([]); + this.state = useState({newTodo: ""}); + //this.inputRef = useRef("new_todo"); + useAutoFocus("new_todo"); + /*onMounted(() => { + this.inputRef.el.focus(); + });*/ + } + + addTodo(ev) { + if(ev.keyCode === 13 && this.state.newTodo){ + this.todos.push({ + id: (this.todos.length > 0 ? ((Math.max(...this.todos.map(t => t.id))) + 1) : 0), + description: this.state.newTodo, + isCompleted: false + }); + this.state.newTodo = ""; + } + } + +} diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..f06988808ac --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,11 @@ + + + + +
+ + +
+
+ +
\ No newline at end of file diff --git a/awesome_owl/utils.js b/awesome_owl/utils.js new file mode 100644 index 00000000000..63773b05e5d --- /dev/null +++ b/awesome_owl/utils.js @@ -0,0 +1,9 @@ +import { useEffect, useRef } from "@odoo/owl"; + +export function useAutoFocus(name){ + let ref = useRef(name); + useEffect( + (el) => el && el.focus(), + () => [ref.el] + ); +} From 491e8c0cdcdd477b5b4fc12319cedcce3f09aa99 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 24 Feb 2025 12:10:52 +0100 Subject: [PATCH 20/27] [IMP] awesome_owl: 10-14 --- awesome_owl/static/src/card/card.js | 10 +++++++++- awesome_owl/static/src/card/card.xml | 5 ++--- awesome_owl/static/src/playground.xml | 5 +++-- awesome_owl/static/src/todo/todo_item.js | 4 +++- awesome_owl/static/src/todo/todo_item.xml | 7 +++++-- awesome_owl/static/src/todo/todo_list.js | 19 +++++++++++++------ awesome_owl/static/src/todo/todo_list.xml | 6 +++++- awesome_owl/{ => static/src}/utils.js | 0 8 files changed, 40 insertions(+), 16 deletions(-) rename awesome_owl/{ => static/src}/utils.js (100%) diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index 686f9aac4be..3260a1ee316 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -7,6 +7,14 @@ export class Card extends Component { static props = { title: {type: String}, - content: {type: String} + slots: {type: Object, optional: true}, }; + + setup(){ + this.state = useState({ contentVisible: true }); + } + + toggleContent(){ + this.state.contentVisible ^= true; + } } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index 144928be8a3..012d29e4a8c 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -7,9 +7,8 @@
-

- -

+ + diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 74bda6a6052..1a0ea03a584 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -7,8 +7,9 @@
- - + + +

The sum is:

diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js index de9267d507d..0c186817fdb 100644 --- a/awesome_owl/static/src/todo/todo_item.js +++ b/awesome_owl/static/src/todo/todo_item.js @@ -6,6 +6,8 @@ export class TodoItem extends Component{ static template = "awesome_owl.todo_item"; static props = { - todo: {type: Object} + todo: {type: Object}, + toggleState: {type: Function}, + removeTodo: {type: Function}, }; } diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml index 6cf82a1472c..dc2590106f6 100644 --- a/awesome_owl/static/src/todo/todo_item.xml +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -1,7 +1,10 @@ - -
.
+
+ + . + +
diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js index 3dcde3ce7ee..5f40f6ffce2 100644 --- a/awesome_owl/static/src/todo/todo_list.js +++ b/awesome_owl/static/src/todo/todo_list.js @@ -2,7 +2,7 @@ import { onMounted, useRef, useState, Component } from "@odoo/owl"; import { TodoItem } from "./todo_item"; -import { useAutoFocus } from "../../../utils"; +import { useAutoFocus } from "../utils"; export class TodoList extends Component { static template = "awesome_owl.todo_list"; @@ -10,13 +10,12 @@ export class TodoList extends Component { static components = { TodoItem }; setup() { - this.todos = useState([]); + this.todos = useState([ + { id: 3, description: "buy milk", isCompleted: false }, + { id: 4, description: "testt", isCompleted: true }, + ]); this.state = useState({newTodo: ""}); - //this.inputRef = useRef("new_todo"); useAutoFocus("new_todo"); - /*onMounted(() => { - this.inputRef.el.focus(); - });*/ } addTodo(ev) { @@ -30,4 +29,12 @@ export class TodoList extends Component { } } + toggleState(id){ + this.todos[this.todos.findIndex(todo => todo.id === id)].isCompleted ^= true; + } + + removeTodo(id){ + this.todos.splice(this.todos.findIndex(todo => todo.id === id), 1); + } + } diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml index f06988808ac..0fb48a99c2d 100644 --- a/awesome_owl/static/src/todo/todo_list.xml +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -4,7 +4,11 @@
- +
diff --git a/awesome_owl/utils.js b/awesome_owl/static/src/utils.js similarity index 100% rename from awesome_owl/utils.js rename to awesome_owl/static/src/utils.js From 90248c9fac5c4375c8ccc51d9498f7aec1c9330f Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 24 Feb 2025 17:25:49 +0100 Subject: [PATCH 21/27] [IMP] awesome_dashboard : Chapter 1 to 5 --- awesome_dashboard/static/src/card/card.js | 16 +++++++++ awesome_dashboard/static/src/card/card.xml | 12 +++++++ awesome_dashboard/static/src/dashboard.js | 36 ++++++++++++++++++++- awesome_dashboard/static/src/dashboard.scss | 7 ++++ awesome_dashboard/static/src/dashboard.xml | 20 +++++++++++- awesome_dashboard/static/src/statistics.js | 30 +++++++++++++++++ 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 awesome_dashboard/static/src/card/card.js create mode 100644 awesome_dashboard/static/src/card/card.xml create mode 100644 awesome_dashboard/static/src/dashboard.scss create mode 100644 awesome_dashboard/static/src/statistics.js diff --git a/awesome_dashboard/static/src/card/card.js b/awesome_dashboard/static/src/card/card.js new file mode 100644 index 00000000000..a881f2d03bd --- /dev/null +++ b/awesome_dashboard/static/src/card/card.js @@ -0,0 +1,16 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_dashboard.card"; + + static props = { + size: {type: Number, optional: true}, + slots: {type: Object, optional: true}, + }; + + static defaultProps = { + size: 1, + }; +} diff --git a/awesome_dashboard/static/src/card/card.xml b/awesome_dashboard/static/src/card/card.xml new file mode 100644 index 00000000000..387494c7c7e --- /dev/null +++ b/awesome_dashboard/static/src/card/card.xml @@ -0,0 +1,12 @@ + + + + +
+
+ +
+
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 637fa4bb972..287f3494336 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,10 +1,44 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, onWillStart, useState } from "@odoo/owl"; import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { Card } from "./card/card"; +import { rpc } from "@web/core/network/rpc"; +import { loadStatistics } from "./statistics"; + class AwesomeDashboard extends Component { + static components = { Layout, Card }; static template = "awesome_dashboard.AwesomeDashboard"; + + setup(){ + this.action = useService("action"); + this.state = useState({ statistics: [] }); + onWillStart(async () => { + //this.state.statistics = await rpc("/awesome_dashboard/statistics"); + const stats = await loadStatistics(); + //console.log(stats().then( result => { this.state.statistics = result; } )); + console.log(stats); + }); + } + + openCustomers(){ + this.action.doAction("base.action_partner_form"); + } + + async openLeads(activity){ + this.action.doAction({ + type: 'ir.actions.act_window', + name: 'Leads', + target: 'current', + res_id: activity.res_id, + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']] + }); + } + } registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.scss b/awesome_dashboard/static/src/dashboard.scss new file mode 100644 index 00000000000..535010ff899 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard.scss @@ -0,0 +1,7 @@ +.o_dashboard{ + background-color: grey; +} + +.green{ + color: green; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 1a2ac9a2fed..af8a18c43a9 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -2,7 +2,25 @@ - hello dashboard +
+ + +
+ + + + +

+
+
diff --git a/awesome_dashboard/static/src/statistics.js b/awesome_dashboard/static/src/statistics.js new file mode 100644 index 00000000000..5409eb005a6 --- /dev/null +++ b/awesome_dashboard/static/src/statistics.js @@ -0,0 +1,30 @@ +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 statistics = await rpc("/awesome_dashboard/statistics"); + //console.log(statistics); + //return statistics; + + const test = memoize(function loadStatistics(){ + return statistics; + }); + return test; + + /*const statistics = memoize(async function loadStatistics(){ + return await rpc("/awesome_dashboard/statistics"); + }); + + return statistics;*/ + +} + +export const statisticsService = { + start() { + return { loadStatistics }; + } +}; + +registry.category("services").add("statistics", statisticsService); From f642678581430af62d501beaf9492433aad28b07 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Tue, 25 Feb 2025 16:51:49 +0100 Subject: [PATCH 22/27] [IMP] awesome_dashboard : Chapter 5 to 7 --- awesome_dashboard/static/src/dashboard.js | 44 +++++++++++++++---- awesome_dashboard/static/src/dashboard.xml | 17 +++---- .../dashboard_item.js} | 5 ++- .../dashboard_item.xml} | 2 +- .../static/src/pie_chart/pie_chart.js | 30 +++++++++++++ .../static/src/pie_chart/pie_chart.xml | 8 ++++ awesome_dashboard/static/src/statistics.js | 35 +++++++-------- awesome_owl/static/src/playground.js | 6 +-- awesome_owl/static/src/todo/todo_list.xml | 2 +- 9 files changed, 103 insertions(+), 46 deletions(-) rename awesome_dashboard/static/src/{card/card.js => dashboard_item/dashboard_item.js} (70%) rename awesome_dashboard/static/src/{card/card.xml => dashboard_item/dashboard_item.xml} (85%) create mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.js create mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.xml diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 287f3494336..3d3d6283d76 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -4,23 +4,49 @@ import { Component, onWillStart, useState } from "@odoo/owl"; import { registry } from "@web/core/registry"; import { Layout } from "@web/search/layout"; import { useService } from "@web/core/utils/hooks"; -import { Card } from "./card/card"; -import { rpc } from "@web/core/network/rpc"; -import { loadStatistics } from "./statistics"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { PieChart } from "./pie_chart/pie_chart"; class AwesomeDashboard extends Component { - static components = { Layout, Card }; + static components = { Layout, DashboardItem, PieChart }; static template = "awesome_dashboard.AwesomeDashboard"; setup(){ this.action = useService("action"); - this.state = useState({ statistics: [] }); + this.state = useState({ statistics: [], test: {} }); + this.serviceStats = useService("statistics"); + onWillStart(async () => { - //this.state.statistics = await rpc("/awesome_dashboard/statistics"); - const stats = await loadStatistics(); - //console.log(stats().then( result => { this.state.statistics = result; } )); - console.log(stats); + /*const data = await this.serviceStats.loadStatistics(); + console.log(data); + if(!data.orders_by_size.hasOwnProperty("datasets")){ + data.orders_by_size = { + datasets: [{ + data: [ + data.orders_by_size.m, + data.orders_by_size.s, + data.orders_by_size.xl + ] + }], + labels: ['m', 's', 'xl'] + }; + }*/ + this.state.test = await this.serviceStats.loadStatistics(); + console.log(this.state.test); + if(!this.state.test.orders_by_size.hasOwnProperty("datasets")){ + this.state.test.orders_by_size = { + datasets: [{ + data: [ + this.state.test.orders_by_size.m, + this.state.test.orders_by_size.s, + this.state.test.orders_by_size.xl + ] + }], + labels: ['m', 's', 'xl'] + }; + } + this.state.statistics = this.state.test; }); } diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index af8a18c43a9..f0e40229880 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -7,19 +7,14 @@ - - +

-
+ + + + +
diff --git a/awesome_dashboard/static/src/card/card.js b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js similarity index 70% rename from awesome_dashboard/static/src/card/card.js rename to awesome_dashboard/static/src/dashboard_item/dashboard_item.js index a881f2d03bd..957bc0a8ce9 100644 --- a/awesome_dashboard/static/src/card/card.js +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js @@ -2,8 +2,9 @@ import { Component, useState } from "@odoo/owl"; -export class Card extends Component { - static template = "awesome_dashboard.card"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.dashboard_item"; static props = { size: {type: Number, optional: true}, diff --git a/awesome_dashboard/static/src/card/card.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml similarity index 85% rename from awesome_dashboard/static/src/card/card.xml rename to awesome_dashboard/static/src/dashboard_item/dashboard_item.xml index 387494c7c7e..d801dfd621e 100644 --- a/awesome_dashboard/static/src/card/card.xml +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -1,7 +1,7 @@ - +
diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/pie_chart/pie_chart.js new file mode 100644 index 00000000000..3e62ebb8777 --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.js @@ -0,0 +1,30 @@ +/** @odoo-module **/ + +import { Component, onWillStart, useRef, useEffect } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + + +export class PieChart extends Component { + static template = "awesome_owl.pie_chart"; + + static props = { data: Object }; + + setup() { + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + useEffect(() => this.renderChart(), () => []); + } + + renderChart(){ + console.log("render chart"); + if(this.chart){ + this.chart.destroy(); + } + const ctx = this.canvasRef.el.getContext("2d"); + this.chart = new Chart(ctx, { + type: "pie", + data: this.props.data, + }); + console.log(JSON.stringify(this.props.data)); + } +} diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..48161459cbf --- /dev/null +++ b/awesome_dashboard/static/src/pie_chart/pie_chart.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/awesome_dashboard/static/src/statistics.js b/awesome_dashboard/static/src/statistics.js index 5409eb005a6..30ca0c7abaa 100644 --- a/awesome_dashboard/static/src/statistics.js +++ b/awesome_dashboard/static/src/statistics.js @@ -1,29 +1,26 @@ import { registry } from "@web/core/registry"; import { rpc } from "@web/core/network/rpc"; import { memoize } from "@web/core/utils/functions"; +import { reactive } from "@odoo/owl"; -export async function loadStatistics(){ - const statistics = await rpc("/awesome_dashboard/statistics"); - //console.log(statistics); - //return statistics; - - const test = memoize(function loadStatistics(){ - return statistics; - }); - return test; - - /*const statistics = memoize(async function loadStatistics(){ - return await rpc("/awesome_dashboard/statistics"); - }); - - return statistics;*/ - -} - export const statisticsService = { + async: ['loadStatistics'], start() { - return { loadStatistics }; + /*return { + loadStatistics: memoize(() => rpc("/awesome_dashboard/statistics")), + }*/ + let data = { orders_by_size: {} }; + const callRpc = async function(){ + data = await memoize(() => rpc("/awesome_dashboard/statistics"))(); + console.log(data); + } + callRpc(); + setInterval(callRpc, 5000); + + return reactive({ + loadStatistics: (() => data), + }) } }; diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 8115ed6eec5..5bfbe920e96 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -12,9 +12,9 @@ export class Playground extends Component { setup(){ this.state = useState({ - card1: { title: "card 1", content: "content of card 1" }, - card2: { title: "card 2", content: "content of card 2" }, - sum: 2 + card1: { title: "card 1", content: "content of card 1" }, + card2: { title: "card 2", content: "content of card 2" }, + sum: 2 }); this.content_value_escaped = "
some content
"; diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml index 0fb48a99c2d..6cc64afdb04 100644 --- a/awesome_owl/static/src/todo/todo_list.xml +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -12,4 +12,4 @@
- \ No newline at end of file + From 93121a350667dc60e2dcdbee83cacf88187a30d2 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Wed, 26 Feb 2025 15:25:43 +0100 Subject: [PATCH 23/27] [IMP] awesome_dashboard : Chapter 7 to end --- awesome_dashboard/__manifest__.py | 4 + awesome_dashboard/static/src/dashboard.js | 70 --------------- awesome_dashboard/static/src/dashboard.xml | 21 ----- .../static/src/dashboard/dashboard.js | 89 +++++++++++++++++++ .../static/src/{ => dashboard}/dashboard.scss | 0 .../static/src/dashboard/dashboard.xml | 41 +++++++++ .../dashboard_item/dashboard_item.js | 0 .../dashboard_item/dashboard_item.xml | 0 .../static/src/dashboard/dashboard_items.js | 66 ++++++++++++++ .../src/dashboard/number_card/number_card.js | 13 +++ .../src/dashboard/number_card/number_card.xml | 9 ++ .../src/dashboard/pie_chart/pie_chart.js | 38 ++++++++ .../{ => dashboard}/pie_chart/pie_chart.xml | 0 .../pie_chart_card/pie_chart_card.js | 38 ++++++++ .../pie_chart_card/pie_chart_card.xml | 9 ++ .../static/src/dashboard/statistics.js | 22 +++++ .../static/src/dashboard_action.js | 16 ++++ .../static/src/pie_chart/pie_chart.js | 30 ------- awesome_dashboard/static/src/statistics.js | 27 ------ 19 files changed, 345 insertions(+), 148 deletions(-) delete mode 100644 awesome_dashboard/static/src/dashboard.js delete mode 100644 awesome_dashboard/static/src/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js 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}/dashboard_item/dashboard_item.js (100%) rename awesome_dashboard/static/src/{ => dashboard}/dashboard_item/dashboard_item.xml (100%) create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js rename awesome_dashboard/static/src/{ => dashboard}/pie_chart/pie_chart.xml (100%) create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/statistics.js create mode 100644 awesome_dashboard/static/src/dashboard_action.js delete mode 100644 awesome_dashboard/static/src/pie_chart/pie_chart.js delete mode 100644 awesome_dashboard/static/src/statistics.js diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..0beaa1c2a35 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -25,6 +25,10 @@ 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', ], + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*' + ], + }, 'license': 'AGPL-3' } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 3d3d6283d76..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,70 +0,0 @@ -/** @odoo-module **/ - -import { Component, onWillStart, useState } from "@odoo/owl"; -import { registry } from "@web/core/registry"; -import { Layout } from "@web/search/layout"; -import { useService } from "@web/core/utils/hooks"; -import { DashboardItem } from "./dashboard_item/dashboard_item"; -import { PieChart } from "./pie_chart/pie_chart"; - - -class AwesomeDashboard extends Component { - static components = { Layout, DashboardItem, PieChart }; - static template = "awesome_dashboard.AwesomeDashboard"; - - setup(){ - this.action = useService("action"); - this.state = useState({ statistics: [], test: {} }); - this.serviceStats = useService("statistics"); - - onWillStart(async () => { - /*const data = await this.serviceStats.loadStatistics(); - console.log(data); - if(!data.orders_by_size.hasOwnProperty("datasets")){ - data.orders_by_size = { - datasets: [{ - data: [ - data.orders_by_size.m, - data.orders_by_size.s, - data.orders_by_size.xl - ] - }], - labels: ['m', 's', 'xl'] - }; - }*/ - this.state.test = await this.serviceStats.loadStatistics(); - console.log(this.state.test); - if(!this.state.test.orders_by_size.hasOwnProperty("datasets")){ - this.state.test.orders_by_size = { - datasets: [{ - data: [ - this.state.test.orders_by_size.m, - this.state.test.orders_by_size.s, - this.state.test.orders_by_size.xl - ] - }], - labels: ['m', 's', 'xl'] - }; - } - this.state.statistics = this.state.test; - }); - } - - openCustomers(){ - this.action.doAction("base.action_partner_form"); - } - - async openLeads(activity){ - this.action.doAction({ - type: 'ir.actions.act_window', - name: 'Leads', - target: 'current', - res_id: activity.res_id, - res_model: 'crm.lead', - views: [[false, 'list'], [false, 'form']] - }); - } - -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index f0e40229880..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - -
- - -
- - - -

-
- - - - -
-
- -
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..10e0ed27fc1 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,89 @@ +/** @odoo-module **/ + +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { PieChart } from "./pie_chart/pie_chart"; +import { items } from "./dashboard_items"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; + + +class AwesomeDashboard extends Component { + static components = { Layout, DashboardItem, PieChart }; + static template = "awesome_dashboard.AwesomeDashboard"; + + setup(){ + this.action = useService("action"); + this.statistics = useState(useService("statistics")); + this.items = registry.category("awesome_dashboard").getAll(); + this.dialog = useService("dialog"); + this.state = useState({ + disabledItems: browser.localStorage.getItem("disabledDashboardItems")?.split(",") || [] + }); + } + + openCustomers(){ + this.action.doAction("base.action_partner_form"); + } + + async openLeads(activity){ + this.action.doAction({ + type: 'ir.actions.act_window', + name: 'Leads', + target: 'current', + res_id: activity.res_id, + res_model: 'crm.lead', + views: [[false, 'list'], [false, 'form']] + }); + } + + openSettings(){ + this.dialog.add(ConfigurationDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + onUpdateConfiguration: this.updateConfiguration.bind(this), + }); + } + + updateConfiguration(newDisabledItems){ + this.state.disabledItems = newDisabledItems; + } + +} + +class ConfigurationDialog extends Component { + static template = "awesome_dashboard.settings_dialog"; + static components = { Dialog, CheckBox }; + static props = ["items", "close", "disabledItems", "onUpdateConfiguration"]; + + setup(){ + this.items = useState(this.props.items.map((item) => { + return { + ...item, + enabled: !this.props.disabledItems.includes(item.id), + } + })); + } + + onChange(checked, changedItem){ + changedItem.enabled = checked; + const newDisabledItems = Object.values(this.items).filter( + (item) => !item.enabled + ).map((item) => item.id); + + browser.localStorage.setItem("disabledDashboardItems", newDisabledItems); + + this.props.onUpdateConfiguration(newDisabledItems); + } + + done(){ + this.props.close(); + } + +} + +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..3f76897b222 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,41 @@ + + + + +
+ + + + +
+ +
+ + + + + + +
+
+
+ + + + Wich cards do you wish to see ? + + + + + + + + + + + +
diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js similarity index 100% rename from awesome_dashboard/static/src/dashboard_item/dashboard_item.js rename to awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml similarity index 100% rename from awesome_dashboard/static/src/dashboard_item/dashboard_item.xml rename to awesome_dashboard/static/src/dashboard/dashboard_item/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..0862bad2362 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,66 @@ +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { registry } from "@web/core/registry"; + + +const items = [ + { + id: "average_quantity", + description: "Average amount of t-shirt", + Component: NumberCard, + props: (data) => ({ + title: "Average amount of t-shirt by order this month", + value: data.average_quantity + }), + }, + { + id: "average_time", + description: "Average time", + Component: NumberCard, + props: (data) => ({ + title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + value: data.average_time + }), + }, + { + id: "nb_new_orders", + description: "Number of new orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Number of new orders this month", + value: data.nb_new_orders + }), + }, + { + id: "nb_cancelled_orders", + description: "Number of cancelled orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Number of cancelled orders this month", + value: data.nb_cancelled_orders + }), + }, + { + id: "total_amount", + description: "Total amount of new orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Total amount of new orders this month", + value: data.total_amount + }), + }, + { + id: "order_by_size", + description: "Shirt orders by size", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "Shirt orders by size", + value: data.orders_by_size + }), + } +]; + +items.forEach(item => { + registry.category("awesome_dashboard").add(item.id, item); +}); diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..4a64d289771 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,13 @@ +/** @odoo-module **/ + +import { Component } from "@odoo/owl"; + + +export class NumberCard extends Component{ + static template = "awesome_dashboard.number_card"; + + static props = { + title: {type: String, optional: true}, + value: {type: Number, optional: true}, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..34eed7a3101 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,9 @@ + + + + +

+

+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js new file mode 100644 index 00000000000..d4e400fd26e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,38 @@ +/** @odoo-module **/ + +import { Component, onWillStart, useRef, onMounted, onWillUnmount } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + + +export class PieChart extends Component { + static template = "awesome_owl.pie_chart"; + + static props = { data: Object }; + + setup() { + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(() => this.renderChart()); + onWillUnmount(() => this.chart.destroy()); + } + + renderChart(){ + if(this.chart){ + this.chart.destroy(); + } + const labels = Object.keys(this.props.data); + const data = Object.values(this.props.data); + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + label: this.props.label, + data: data, + } + ] + } + }); + } +} diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml similarity index 100% rename from awesome_dashboard/static/src/pie_chart/pie_chart.xml rename to awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js new file mode 100644 index 00000000000..b9a964abcc7 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,38 @@ +/** @odoo-module **/ + +import { Component, useRef, onWillStart, onMounted, onWillUnmount } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + + +export class PieChartCard extends Component{ + static template = "awesome_dashboard.pie_chart_card"; + + static props = { + title: {type: String, optional: true}, + value: {type: Object, optional: true}, + }; + + setup(){ + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(() => this.renderChart()); + onWillUnmount(() => this.chart.destroy()); + } + + renderChart(){ + const labels = Object.keys(this.props.value); + const data = Object.values(this.props.value); + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + label: this.props.label, + data: data, + } + ] + } + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml new file mode 100644 index 00000000000..f6d281df91e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,9 @@ + + + + +

+ +
+ +
diff --git a/awesome_dashboard/static/src/dashboard/statistics.js b/awesome_dashboard/static/src/dashboard/statistics.js new file mode 100644 index 00000000000..06365dbe280 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics.js @@ -0,0 +1,22 @@ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + + +export const statisticsService = { + start() { + const data = reactive({ isReady: false }); + + async function loadData(){ + const updates = await rpc("/awesome_dashboard/statistics"); + Object.assign(data, updates, {isReady: true}); + } + + setInterval(loadData, 5000); + loadData(); + + return data; + } +}; + +registry.category("services").add("statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..fd7186fe05a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,16 @@ +/** @odoo-module **/ + +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; +import { Component, xml } from "@odoo/owl"; + + +export class DashboardAction extends Component { + static components = { LazyComponent }; + static template = xml` + + `; + +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashboardAction); diff --git a/awesome_dashboard/static/src/pie_chart/pie_chart.js b/awesome_dashboard/static/src/pie_chart/pie_chart.js deleted file mode 100644 index 3e62ebb8777..00000000000 --- a/awesome_dashboard/static/src/pie_chart/pie_chart.js +++ /dev/null @@ -1,30 +0,0 @@ -/** @odoo-module **/ - -import { Component, onWillStart, useRef, useEffect } from "@odoo/owl"; -import { loadJS } from "@web/core/assets"; - - -export class PieChart extends Component { - static template = "awesome_owl.pie_chart"; - - static props = { data: Object }; - - setup() { - this.canvasRef = useRef("canvas"); - onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); - useEffect(() => this.renderChart(), () => []); - } - - renderChart(){ - console.log("render chart"); - if(this.chart){ - this.chart.destroy(); - } - const ctx = this.canvasRef.el.getContext("2d"); - this.chart = new Chart(ctx, { - type: "pie", - data: this.props.data, - }); - console.log(JSON.stringify(this.props.data)); - } -} diff --git a/awesome_dashboard/static/src/statistics.js b/awesome_dashboard/static/src/statistics.js deleted file mode 100644 index 30ca0c7abaa..00000000000 --- a/awesome_dashboard/static/src/statistics.js +++ /dev/null @@ -1,27 +0,0 @@ -import { registry } from "@web/core/registry"; -import { rpc } from "@web/core/network/rpc"; -import { memoize } from "@web/core/utils/functions"; -import { reactive } from "@odoo/owl"; - - -export const statisticsService = { - async: ['loadStatistics'], - start() { - /*return { - loadStatistics: memoize(() => rpc("/awesome_dashboard/statistics")), - }*/ - let data = { orders_by_size: {} }; - const callRpc = async function(){ - data = await memoize(() => rpc("/awesome_dashboard/statistics"))(); - console.log(data); - } - callRpc(); - setInterval(callRpc, 5000); - - return reactive({ - loadStatistics: (() => data), - }) - } -}; - -registry.category("services").add("statistics", statisticsService); From bdcb1c6be9e4ff8513dc786cbf09cf3e59c0a34e Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Wed, 26 Feb 2025 17:27:37 +0100 Subject: [PATCH 24/27] [IMP] awesome_clicker: Chapter 1 to 5 --- awesome_clicker/static/src/clicker_hook.js | 7 +++++ awesome_clicker/static/src/clicker_service.js | 18 +++++++++++ .../static/src/clicker_systray_item.js | 31 +++++++++++++++++++ .../static/src/clicker_systray_item.xml | 9 ++++++ .../static/src/client_action/client_action.js | 20 ++++++++++++ .../src/client_action/client_action.xml | 10 ++++++ 6 files changed, 95 insertions(+) create mode 100644 awesome_clicker/static/src/clicker_hook.js create mode 100644 awesome_clicker/static/src/clicker_service.js create mode 100644 awesome_clicker/static/src/clicker_systray_item.js create mode 100644 awesome_clicker/static/src/clicker_systray_item.xml create mode 100644 awesome_clicker/static/src/client_action/client_action.js create mode 100644 awesome_clicker/static/src/client_action/client_action.xml diff --git a/awesome_clicker/static/src/clicker_hook.js b/awesome_clicker/static/src/clicker_hook.js new file mode 100644 index 00000000000..120cd0daaf0 --- /dev/null +++ b/awesome_clicker/static/src/clicker_hook.js @@ -0,0 +1,7 @@ +import { useService } from "@web/core/utils/hooks"; +import { useState } from "@odoo/owl"; + + +export function useClicker() { + return useState(useService("clickerService")); +} diff --git a/awesome_clicker/static/src/clicker_service.js b/awesome_clicker/static/src/clicker_service.js new file mode 100644 index 00000000000..323a9d1d1da --- /dev/null +++ b/awesome_clicker/static/src/clicker_service.js @@ -0,0 +1,18 @@ +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; + + +const state = reactive({ clicks: 0 }); + +export const clickerService = { + start(){ + return { + state, + increment(inc) { + state.clicks += inc; + } + } + } +}; + +registry.category("services").add("clickerService", clickerService); diff --git a/awesome_clicker/static/src/clicker_systray_item.js b/awesome_clicker/static/src/clicker_systray_item.js new file mode 100644 index 00000000000..bd219c4978a --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item.js @@ -0,0 +1,31 @@ +import { registry } from "@web/core/registry"; +import { Component, useState, useExternalListener } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { useClicker } from "./clicker_hook"; + + +class Clicker extends Component { + static template = "awesome_clicker.clicker_systray_item"; + + setup(){ + this.clicker = useClicker(); + useExternalListener(document.body, "click", () => this.clicker.increment(1), { capture: true }); + this.action = useService("action"); + } + + increment(){ + this.clicker.increment(9); + } + + openClientAction(){ + this.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game" + }); + } + +} + +registry.category("systray").add("awesome_clicker.Clicker", { Component: Clicker }); diff --git a/awesome_clicker/static/src/clicker_systray_item.xml b/awesome_clicker/static/src/clicker_systray_item.xml new file mode 100644 index 00000000000..9fdb34c2cb0 --- /dev/null +++ b/awesome_clicker/static/src/clicker_systray_item.xml @@ -0,0 +1,9 @@ + + + +
+ Clicks: + +
+
+
diff --git a/awesome_clicker/static/src/client_action/client_action.js b/awesome_clicker/static/src/client_action/client_action.js new file mode 100644 index 00000000000..0a8b81c9c39 --- /dev/null +++ b/awesome_clicker/static/src/client_action/client_action.js @@ -0,0 +1,20 @@ +import { Component, useState } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; +import { registry } from "@web/core/registry"; +import { useClicker } from "../clicker_hook"; + + +export class ClientAction extends Component { + static template = "awesome_clicker.client_action"; + + setup() { + this.clicker = useClicker(); + } + + increment() { + this.clicker.increment(9); + } + +} + +registry.category("actions").add("awesome_clicker.client_action", ClientAction); diff --git a/awesome_clicker/static/src/client_action/client_action.xml b/awesome_clicker/static/src/client_action/client_action.xml new file mode 100644 index 00000000000..94d1434af8c --- /dev/null +++ b/awesome_clicker/static/src/client_action/client_action.xml @@ -0,0 +1,10 @@ + + + + +
+ Clicks: + +
+
+
From 0751346de22ddce9f805895d07f7af4cdb3e9448 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Fri, 28 Feb 2025 09:50:51 +0100 Subject: [PATCH 25/27] [IMP] awesome_clicker: Chapter 5 to 16 --- awesome_clicker/static/src/click_reward.js | 34 +++++ .../static/src/click_value/click_value.js | 16 +++ .../static/src/click_value/click_value.xml | 10 ++ awesome_clicker/static/src/clicker_model.js | 120 ++++++++++++++++++ .../static/src/clicker_provider.js | 31 +++++ awesome_clicker/static/src/clicker_service.js | 55 ++++++-- .../static/src/clicker_systray_item.js | 5 +- .../static/src/clicker_systray_item.xml | 2 +- .../static/src/client_action/client_action.js | 7 +- .../src/client_action/client_action.xml | 71 ++++++++++- .../form_controller/form_controller_patch.js | 16 +++ awesome_clicker/static/src/utils.js | 7 + 12 files changed, 355 insertions(+), 19 deletions(-) create mode 100644 awesome_clicker/static/src/click_reward.js create mode 100644 awesome_clicker/static/src/click_value/click_value.js create mode 100644 awesome_clicker/static/src/click_value/click_value.xml create mode 100644 awesome_clicker/static/src/clicker_model.js create mode 100644 awesome_clicker/static/src/clicker_provider.js create mode 100644 awesome_clicker/static/src/form_controller/form_controller_patch.js create mode 100644 awesome_clicker/static/src/utils.js diff --git a/awesome_clicker/static/src/click_reward.js b/awesome_clicker/static/src/click_reward.js new file mode 100644 index 00000000000..451ab41a4f1 --- /dev/null +++ b/awesome_clicker/static/src/click_reward.js @@ -0,0 +1,34 @@ +import { choose } from "./utils"; + + +const rewards = [ + { + description: "Get 1 click bot", + apply(clicker) { + clicker.increment(1); + }, + maxLevel: 3, + }, + { + description: "Get 10 click bots", + apply(clicker) { + clicker.increment(10); + }, + minLevel: 3, + maxLevel: 4, + }, + { + description: "Increase bot power", + apply(clicker) { + clicker.power += 1; + }, + minLevel: 3, + }, +]; + +export function getReward(level){ + const availableRewards = rewards.filter((reward) => { + return (level >= (reward?.minLevel || level)) && (level <= (reward?.maxLevel || level)); + }); + return choose(availableRewards); +} diff --git a/awesome_clicker/static/src/click_value/click_value.js b/awesome_clicker/static/src/click_value/click_value.js new file mode 100644 index 00000000000..b8be5f33296 --- /dev/null +++ b/awesome_clicker/static/src/click_value/click_value.js @@ -0,0 +1,16 @@ +import { Component } from "@odoo/owl"; +import { humanNumber } from "@web/core/utils/numbers"; +import { useClicker } from "../clicker_hook"; + + +export class ClickValue extends Component { + static template = "awesome_clicker.click_value"; + + setup() { + this.clicker = useClicker(); + } + + get clicks(){ + return humanNumber(this.clicker.clicks, { decimals: 1 }); + } +} diff --git a/awesome_clicker/static/src/click_value/click_value.xml b/awesome_clicker/static/src/click_value/click_value.xml new file mode 100644 index 00000000000..7282c516501 --- /dev/null +++ b/awesome_clicker/static/src/click_value/click_value.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/awesome_clicker/static/src/clicker_model.js b/awesome_clicker/static/src/clicker_model.js new file mode 100644 index 00000000000..0567445371c --- /dev/null +++ b/awesome_clicker/static/src/clicker_model.js @@ -0,0 +1,120 @@ +/* @odoo-module */ +import { Reactive } from "@web/core/utils/reactive"; +import { EventBus } from "@odoo/owl"; +import { getReward } from "./click_reward"; + + +export class ClickerModel extends Reactive { + constructor(){ + super(); + this._clicks = 0; + this._level = 0; + this._power = 1; + this._bots = { + clickBot: { + price: 1000, + level: 1, + increment: 10, + purchased: 0, + }, + bigBot: { + price: 5000, + level: 2, + increment: 100, + purchased: 0, + }, + }; + this.trees = { + pearTree: { + price: 1000000, + level: 4, + produce: "pear", + purchased: 0 + }, + cherryTree: { + price: 1000000, + level: 4, + produce: "cherry", + purchased: 0 + }, + }; + this.fruits = { + pear: 0, + cherry: 0, + }; + this._milestones = [ + { clicks: 1000, unlock: "clickBot" }, + { clicks: 5000, unlock: "bigBot" }, + { clicks: 100000, unlock: "power multiplier" }, + { clicks: 1000000, unlock: "trees" }, + ]; + this._bus = new EventBus(); + this._tick = 0; + } + + setup(){} + + get clicks(){ return this._clicks; } + + get level(){ return this._level; } + + get power() { return this._power; } + set power(pwr){ this._power = pwr; } + + get bots() { return this._bots; } + + get bus(){ return this._bus; } + + increment(inc){ + this._clicks += inc; + if(this._milestones[this._level] + && this._clicks >= this._milestones[this._level].clicks){ + this.bus.trigger("MILESTONE", this._milestones[this._level]); + this._level++; + } + } + + tick(){ + this._tick++; + for(const bot in this._bots){ + this.increment(this._bots[bot].increment * this._power * this._bots[bot].purchased); + } + if(this._tick === 3){ + for(const tree in this.trees){ + this.fruits[this.trees[tree].produce] += this.trees[tree].purchased; + } + this._tick = 0; + } + } + + buyBot(name){ + if(!Object.keys(this._bots).includes(name)){ + throw new Error(`Invalid bot name ${name}`); + } + if(this._clicks < this._bots[name].price){ + return false; + } + this.increment(-this._bots[name].price); + this._bots[name].purchased++; + } + + buyPower(nb){ + if(this._clicks >= 50000 && this._level >= 3){ + this._power += nb; + this.increment(-50000*nb); + } + } + + buyTree(name){ + if(this._clicks >= this.trees[name].price){ + this.trees[name].purchased++; + this.increment(-this.trees[name].price); + } + } + + getReward(){ + const reward = getReward(this._level); + this._bus.trigger("REWARD", reward); + } + +} diff --git a/awesome_clicker/static/src/clicker_provider.js b/awesome_clicker/static/src/clicker_provider.js new file mode 100644 index 00000000000..4a5bc8d95a2 --- /dev/null +++ b/awesome_clicker/static/src/clicker_provider.js @@ -0,0 +1,31 @@ +/* @odoo-module */ +import { registry } from "@web/core/registry"; + + +const clickerProviderRegistry = registry.category("command_provider"); + +clickerProviderRegistry.add("clicker", { + provide: (env, options) => { + return [ + { + action(){ + env.services["clickerService"].buyBot(1); + }, + category: "clicker", + name: "Buy 1 click bot" + }, + { + action(){ + env.services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game" + }); + }, + category: "clicker", + name: "Open Clicker Game" + }, + ] + } +}); diff --git a/awesome_clicker/static/src/clicker_service.js b/awesome_clicker/static/src/clicker_service.js index 323a9d1d1da..ae501692823 100644 --- a/awesome_clicker/static/src/clicker_service.js +++ b/awesome_clicker/static/src/clicker_service.js @@ -1,17 +1,52 @@ import { registry } from "@web/core/registry"; -import { reactive } from "@odoo/owl"; +import { ClickerModel } from "./clicker_model"; +import { EventBus } from "@odoo/owl"; +import { useService } from "@web/core/utils/hooks"; -const state = reactive({ clicks: 0 }); +const clickerService = { + dependencies: ["action", "effect", "notification"], + start(env, services){ + const clicker_model = new ClickerModel(); -export const clickerService = { - start(){ - return { - state, - increment(inc) { - state.clicks += inc; - } - } + const bus = clicker_model.bus; + bus.addEventListener("MILESTONE", (ev) => { + services.effect.add({ + type: "rainbow_man", + message: `Milestone reached! You can now buy ${ev.detail.unlock}`, + }); + }); + + bus.addEventListener("REWARD", (ev) => { + const reward = ev.detail; + const closeNotification = services.notification.add( + `Congrats you won a reward: "${reward.description}"`, + { + type: "success", + sticky: true, + buttons: [ + { + name: "Collect", + onClick: () => { + reward.apply(clicker_model); + closeNotification(); + services.action.doAction({ + type: "ir.actions.client", + tag: "awesome_clicker.client_action", + target: "new", + name: "Clicker Game" + }); + } + } + ] + } + ); + }); + + document.addEventListener("click", () => clicker_model.increment(1), true); + setInterval(() => clicker_model.tick(), 10000); + + return clicker_model; } }; diff --git a/awesome_clicker/static/src/clicker_systray_item.js b/awesome_clicker/static/src/clicker_systray_item.js index bd219c4978a..3b95e55a68e 100644 --- a/awesome_clicker/static/src/clicker_systray_item.js +++ b/awesome_clicker/static/src/clicker_systray_item.js @@ -1,15 +1,16 @@ import { registry } from "@web/core/registry"; -import { Component, useState, useExternalListener } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; import { useClicker } from "./clicker_hook"; +import { ClickValue } from "./click_value/click_value"; class Clicker extends Component { static template = "awesome_clicker.clicker_systray_item"; + static components = { ClickValue }; setup(){ this.clicker = useClicker(); - useExternalListener(document.body, "click", () => this.clicker.increment(1), { capture: true }); this.action = useService("action"); } diff --git a/awesome_clicker/static/src/clicker_systray_item.xml b/awesome_clicker/static/src/clicker_systray_item.xml index 9fdb34c2cb0..db1179ea74f 100644 --- a/awesome_clicker/static/src/clicker_systray_item.xml +++ b/awesome_clicker/static/src/clicker_systray_item.xml @@ -2,7 +2,7 @@
- Clicks: + Clicks:
diff --git a/awesome_clicker/static/src/client_action/client_action.js b/awesome_clicker/static/src/client_action/client_action.js index 0a8b81c9c39..404ddfb5f91 100644 --- a/awesome_clicker/static/src/client_action/client_action.js +++ b/awesome_clicker/static/src/client_action/client_action.js @@ -2,19 +2,18 @@ import { Component, useState } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; import { registry } from "@web/core/registry"; import { useClicker } from "../clicker_hook"; +import { humanNumber } from "@web/core/utils/numbers"; +import { ClickValue } from "../click_value/click_value"; export class ClientAction extends Component { static template = "awesome_clicker.client_action"; + static components = { ClickValue }; setup() { this.clicker = useClicker(); } - increment() { - this.clicker.increment(9); - } - } registry.category("actions").add("awesome_clicker.client_action", ClientAction); diff --git a/awesome_clicker/static/src/client_action/client_action.xml b/awesome_clicker/static/src/client_action/client_action.xml index 94d1434af8c..f2e37176e29 100644 --- a/awesome_clicker/static/src/client_action/client_action.xml +++ b/awesome_clicker/static/src/client_action/client_action.xml @@ -3,8 +3,75 @@
- Clicks: - +
+ Clicks: + + + + + + Level: [] +
+
+

Bots

+
+
+ x + ( + clicks/10 seconds) +
+
+ +
+
+
+
+

Power multiplier

+
+
+ x + +
+
+ +
+
+
+
+

Trees

+
+
+ + x (1x /30 seconds) + +
+
+ +
+
+

Fruits

+
+
+ + x s +
+
+
diff --git a/awesome_clicker/static/src/form_controller/form_controller_patch.js b/awesome_clicker/static/src/form_controller/form_controller_patch.js new file mode 100644 index 00000000000..5642b5816e1 --- /dev/null +++ b/awesome_clicker/static/src/form_controller/form_controller_patch.js @@ -0,0 +1,16 @@ +import { FormController } from "@web/views/form/form_controller"; +import { patch } from "@web/core/utils/patch"; +import { useClicker } from "../clicker_hook"; + + +const formControllerPatch = { + setup(){ + super.setup(...arguments); + if(Math.random() <= 0.01){ + const clicker = useClicker(); + clicker.getReward(); + } + } +}; + +patch(FormController.prototype, formControllerPatch); diff --git a/awesome_clicker/static/src/utils.js b/awesome_clicker/static/src/utils.js new file mode 100644 index 00000000000..b69b433b747 --- /dev/null +++ b/awesome_clicker/static/src/utils.js @@ -0,0 +1,7 @@ +export function choose(array){ + if(array.length === 1){ + return array[0]; + } + const index = Math.floor(Math.random() * (array.length)); + return array[index]; +} From 7c22bab8498a6beb3c3ab23ff873b13374916934 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Fri, 28 Feb 2025 15:35:05 +0100 Subject: [PATCH 26/27] [IMP] awesome_clicker: Chapter 16 to 19 --- awesome_clicker/static/src/clicker_model.js | 30 ++-- awesome_clicker/static/src/clicker_service.js | 14 +- .../static/src/clicker_systray_item.js | 28 +++- .../static/src/clicker_systray_item.xml | 35 ++++- .../static/src/client_action/client_action.js | 3 +- .../src/client_action/client_action.xml | 129 ++++++++++-------- 6 files changed, 165 insertions(+), 74 deletions(-) diff --git a/awesome_clicker/static/src/clicker_model.js b/awesome_clicker/static/src/clicker_model.js index 0567445371c..38dbbbb11d5 100644 --- a/awesome_clicker/static/src/clicker_model.js +++ b/awesome_clicker/static/src/clicker_model.js @@ -5,23 +5,23 @@ import { getReward } from "./click_reward"; export class ClickerModel extends Reactive { - constructor(){ + constructor(state){ super(); - this._clicks = 0; - this._level = 0; - this._power = 1; + this._clicks = state?._clicks || 0; + this._level = state?._level || 0; + this._power = state?._power || 1; this._bots = { clickBot: { price: 1000, level: 1, increment: 10, - purchased: 0, + purchased: state?._bots?.clickBot?.purchased || 0, }, bigBot: { price: 5000, level: 2, increment: 100, - purchased: 0, + purchased: state?._bots?.bigBot?.purchased || 0, }, }; this.trees = { @@ -29,18 +29,18 @@ export class ClickerModel extends Reactive { price: 1000000, level: 4, produce: "pear", - purchased: 0 + purchased: state?.trees?.pearTree?.purchased || 0 }, cherryTree: { price: 1000000, level: 4, produce: "cherry", - purchased: 0 + purchased: state?.trees?.cherryTree?.purchased || 0 }, }; this.fruits = { - pear: 0, - cherry: 0, + pear: state?.fruits?.pear || 0, + cherry: state?.fruits?.cherry || 0, }; this._milestones = [ { clicks: 1000, unlock: "clickBot" }, @@ -49,7 +49,7 @@ export class ClickerModel extends Reactive { { clicks: 1000000, unlock: "trees" }, ]; this._bus = new EventBus(); - this._tick = 0; + this._tick = state?._tick || 0; } setup(){} @@ -63,6 +63,14 @@ export class ClickerModel extends Reactive { get bots() { return this._bots; } + get firePower(){ + let sum = 0; + for(const bot in this._bots){ + sum += this._bots[bot].purchased * this._bots[bot].increment * this._power; + } + return sum; + } + get bus(){ return this._bus; } increment(inc){ diff --git a/awesome_clicker/static/src/clicker_service.js b/awesome_clicker/static/src/clicker_service.js index ae501692823..e712ebc1947 100644 --- a/awesome_clicker/static/src/clicker_service.js +++ b/awesome_clicker/static/src/clicker_service.js @@ -2,12 +2,18 @@ import { registry } from "@web/core/registry"; import { ClickerModel } from "./clicker_model"; import { EventBus } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; +import { browser } from "@web/core/browser/browser"; const clickerService = { dependencies: ["action", "effect", "notification"], start(env, services){ - const clicker_model = new ClickerModel(); + const clicker_state_storage = browser.localStorage.getItem("clicker_state"); + let state = undefined; + if(clicker_state_storage){ + state = JSON.parse(clicker_state_storage); + } + const clicker_model = new ClickerModel(state); const bus = clicker_model.bus; bus.addEventListener("MILESTONE", (ev) => { @@ -44,7 +50,11 @@ const clickerService = { }); document.addEventListener("click", () => clicker_model.increment(1), true); - setInterval(() => clicker_model.tick(), 10000); + setInterval(() => { + clicker_model.tick(); + browser.localStorage.setItem("clicker_state", JSON.stringify(clicker_model)); + }, 10000); + return clicker_model; } diff --git a/awesome_clicker/static/src/clicker_systray_item.js b/awesome_clicker/static/src/clicker_systray_item.js index 3b95e55a68e..92a909186e1 100644 --- a/awesome_clicker/static/src/clicker_systray_item.js +++ b/awesome_clicker/static/src/clicker_systray_item.js @@ -3,11 +3,13 @@ import { Component, useState } from "@odoo/owl"; import { useService } from "@web/core/utils/hooks"; import { useClicker } from "./clicker_hook"; import { ClickValue } from "./click_value/click_value"; +import { Dropdown } from "@web/core/dropdown/dropdown"; +import { DropdownItem } from "@web/core/dropdown/dropdown_item"; class Clicker extends Component { static template = "awesome_clicker.clicker_systray_item"; - static components = { ClickValue }; + static components = { ClickValue, Dropdown, DropdownItem }; setup(){ this.clicker = useClicker(); @@ -27,6 +29,30 @@ class Clicker extends Component { }); } + get numberTrees() { + let sum = 0; + for(const tree in this.clicker.trees){ + sum += this.clicker.trees[tree].purchased; + } + return sum; + } + + get numberFruits() { + let sum = 0; + for(const fruit in this.clicker.fruits){ + sum += this.clicker.fruits[fruit]; + } + return sum; + } + + get numberBots(){ + let sum = 0; + for(const bot in this.clicker.bots){ + sum += this.clicker.bots[bot].purchased; + } + return sum; + } + } registry.category("systray").add("awesome_clicker.Clicker", { Component: Clicker }); diff --git a/awesome_clicker/static/src/clicker_systray_item.xml b/awesome_clicker/static/src/clicker_systray_item.xml index db1179ea74f..0c9b2c3ccdd 100644 --- a/awesome_clicker/static/src/clicker_systray_item.xml +++ b/awesome_clicker/static/src/clicker_systray_item.xml @@ -2,8 +2,39 @@
- Clicks: - + + + + + + + + + + + x + + + + x + + + + x + + + + +
diff --git a/awesome_clicker/static/src/client_action/client_action.js b/awesome_clicker/static/src/client_action/client_action.js index 404ddfb5f91..90204257ea3 100644 --- a/awesome_clicker/static/src/client_action/client_action.js +++ b/awesome_clicker/static/src/client_action/client_action.js @@ -4,11 +4,12 @@ import { registry } from "@web/core/registry"; import { useClicker } from "../clicker_hook"; import { humanNumber } from "@web/core/utils/numbers"; import { ClickValue } from "../click_value/click_value"; +import { Notebook } from "@web/core/notebook/notebook"; export class ClientAction extends Component { static template = "awesome_clicker.client_action"; - static components = { ClickValue }; + static components = { ClickValue, Notebook }; setup() { this.clicker = useClicker(); diff --git a/awesome_clicker/static/src/client_action/client_action.xml b/awesome_clicker/static/src/client_action/client_action.xml index f2e37176e29..9d42c63498c 100644 --- a/awesome_clicker/static/src/client_action/client_action.xml +++ b/awesome_clicker/static/src/client_action/client_action.xml @@ -11,67 +11,82 @@ Level: [] + Total clicks per 10 seconds: []
-
-

Bots

-
-
- x - ( - clicks/10 seconds) -
-
- -
-
-
-
-

Power multiplier

-
-
- x - -
-
- -
-
-
-
-

Trees

-
-
- - x (1x /30 seconds) - + + + + + +
+

Bots

+
+
+ x + ( + clicks/10 seconds) +
+
+ +
+
-
- +
+

Power multiplier

+
+
+ x + +
+
+ +
+
-
-

Fruits

-
-
- - x s + + +
+

Trees

+
+
+ + x (1x /30 seconds) + +
+
+ +
+
+

Fruits

+
+
+ + x s +
+
-
-
+
+
+ + + + +
From 40352eb169b6d76be0d92559cfa5cfb9c22015a2 Mon Sep 17 00:00:00 2001 From: "Romain (roto)" Date: Mon, 3 Mar 2025 15:50:09 +0100 Subject: [PATCH 27/27] [IMP] restrict access data --- estate/__manifest__.py | 6 +++-- estate/security/ir.model.access.csv | 12 ++++++---- estate/security/ir.model.access.old.csv | 5 ++++ estate/security/security.xml | 32 +++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 estate/security/ir.model.access.old.csv create mode 100644 estate/security/security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f5dbd888065..771ebf3f185 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,14 +4,16 @@ 'base', ], 'data': [ - 'security/ir.model.access.csv', - 'views/res_users_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', 'views/estate_menus.xml', + + 'security/security.xml', + 'security/ir.model.access.csv', ], + 'category': 'Real Estate/Brokerage', 'application': True, 'license': 'LGPL-3', } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4073dc03078..b93e27a7d40 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,9 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_model,access_estate_property_model,model_estate_property,base.group_user,1,1,1,1 -access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag_model,access_estate_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer_model,access_estate_property_offer_model,model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_model_manager,access_estate_property_model,model_estate_property,estate_group_manager,1,1,1,0 +access_estate_property_model_agent,access_estate_property_model,model_estate_property,estate_group_user,1,1,1,0 +access_estate_property_type_model_manager,access_estate_property_type_model,model_estate_property_type,estate_group_manager,1,1,1,1 +access_estate_property_type_model_agent,access_estate_property_type_model,model_estate_property_type,estate_group_user,1,0,0,0 +access_estate_property_tag_model_manager,access_estate_property_tag_model,model_estate_property_tag,estate_group_manager,1,1,1,1 +access_estate_property_tag_model_agent,access_estate_property_tag_model,model_estate_property_tag,estate_group_user,1,0,0,0 +access_estate_property_offer_model_manager,access_estate_property_offer_model,model_estate_property_offer,estate_group_manager,1,1,1,1 +access_estate_property_offer_model_agent,access_estate_property_offer_model,model_estate_property_offer,estate_group_user,1,1,1,1 diff --git a/estate/security/ir.model.access.old.csv b/estate/security/ir.model.access.old.csv new file mode 100644 index 00000000000..4073dc03078 --- /dev/null +++ b/estate/security/ir.model.access.old.csv @@ -0,0 +1,5 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property_model,access_estate_property_model,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_model,access_estate_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_model,access_estate_property_offer_model,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 00000000000..0595c37eae7 --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,32 @@ + + + + + + + + + Agent + + The user can manage the properties under his care + + + + Manager + + + The user can do agent things and settings of the real estate App + + + + Agent can only see modify their properties or the unassigned ones + + + + [ + '|', ('user_id', '=', user.id), + ('user_id', '=', False) + ] + + +