From 854ce16a28e29cbe8d9f31b4fcd3335c69288fd7 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Thu, 6 Feb 2025 23:48:56 +0530 Subject: [PATCH 01/13] [ADD] estate: create an estate app Created an estate application for Odoo Added models with basic fields Implemented security rules for models Configured menus and basic views (Form and List) Introduced application structure and architecture overview --- estate/__init__.py | 1 + estate/__manifest__.py | 15 ++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 31 +++++++++++++ estate/security/ir.model.access.csv | 2 + estate/views/estate_menus.xml | 12 +++++ estate/views/estate_property_views.xml | 63 ++++++++++++++++++++++++++ 7 files changed, 125 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..a87333b78dd --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,15 @@ +{ + 'name': 'Estate', + 'description': 'Estate Module', + 'version': '1.0', + 'depends': ['base'], + 'author': 'Shiv Bhadaniya', + 'application': True, + 'auto_install': True, + 'license': 'LGPL-3', + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' + ] +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..e19b73855b7 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,31 @@ +from odoo import fields, models +from datetime import datetime, timedelta +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property Model" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(copy=False, default= datetime.now() + timedelta(days=90)) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection([ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ]) + active = fields.Boolean(default=False) + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ], copy=False, default='new', required=True,) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..4f716b0d7bf --- /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_user,access_estate_property_user,estate.model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..a10eeb571ab --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..3fcfdd61c66 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,63 @@ + + + + Estate + estate.property + list,form + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
From cc6545b76eca2c8eecbfbd6207f20b1b6f8a3476 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Fri, 7 Feb 2025 18:42:23 +0530 Subject: [PATCH 02/13] [IMP] estate: add search and filter Removed auto install Added search options for estate properties. Implemented filters to refine property listings. Added group-by functionality for better data organization. --- estate/__manifest__.py | 1 - estate/views/estate_property_views.xml | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a87333b78dd..bb130cf619a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,7 +5,6 @@ 'depends': ['base'], 'author': 'Shiv Bhadaniya', 'application': True, - 'auto_install': True, 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3fcfdd61c66..04e6ef212f7 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -53,6 +53,8 @@ + + @@ -60,4 +62,26 @@ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + From d0689dab95026b21b62caf6499938bc2915fddbc Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Mon, 10 Feb 2025 18:40:15 +0530 Subject: [PATCH 03/13] [IMP] estate: add relationships, computed fields, and action buttons Implemented many2one, many2many, and one2many relations. Added computed fields and onchanges . Added computed fields for automatic updates. Added action buttons for property sale, cancellation, and offer management. --- estate/__manifest__.py | 5 +- estate/models/__init__.py | 3 + estate/models/estate_property.py | 84 ++++++++++++++++++- estate/models/estate_property_offer.py | 62 ++++++++++++++ estate/models/estate_property_tag.py | 7 ++ estate/models/estate_property_type.py | 7 ++ estate/security/ir.model.access.csv | 3 + estate/views/estate_menus.xml | 6 ++ estate/views/estate_property_offers_views.xml | 25 ++++++ estate/views/estate_property_tags_views.xml | 9 ++ estate/views/estate_property_types_views.xml | 37 ++++++++ estate/views/estate_property_views.xml | 22 +++++ 12 files changed, 268 insertions(+), 2 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_offers_views.xml create mode 100644 estate/views/estate_property_tags_views.xml create mode 100644 estate/views/estate_property_types_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index bb130cf619a..ca0adfc51cc 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,6 +9,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_menus.xml' + 'views/estate_property_types_views.xml', + 'views/estate_property_tags_views.xml', + 'views/estate_property_offers_views.xml', + 'views/estate_menus.xml', ] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e19b73855b7..efddfb9afa5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ -from odoo import fields, models +from odoo import api, fields, models from datetime import datetime, timedelta +from odoo.exceptions import UserError class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Model" @@ -28,4 +29,85 @@ class EstateProperty(models.Model): ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), + ('canceled', 'Canceled'), ], copy=False, default='new', required=True,) + + # Many2one relationship + property_type = fields.Many2one('estate.property.type', string="Property Type") + buyer = fields.Many2one('res.partner', string="Buyer", copy=False) + seller = fields.Many2one('res.users', string="Salesman", default=lambda self: self.env.user) + + # Many2many relationship + tag_ids = fields.Many2many('estate.property.tag', string="Property Tags") + + # One2many relationship + offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offer") + + # Computed fields + total_area = fields.Integer(compute='_compute_total_area', store=True) + + best_price = fields.Float(compute='_compute_best_price', store=True) + + + + + + # ------------------------------------------------------------------------- + # COMPUTE METHODS + # ------------------------------------------------------------------------- + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for data in self: + data.total_area = data.living_area + data.garden_area + + @api.depends('offer_ids') + def _compute_best_price(self): + for data in self: + prices = data.offer_ids.mapped('price') + if prices: + data.best_price = max(prices) + else: + data.best_price = 0.0 + + + + # ------------------------------------------------------------------------- + # ONCHANGE METHODS + # ------------------------------------------------------------------------- + + @api.onchange('garden') + def _onchange_garden(self): + """ Set garden area to 10 and orientation to North when garden is True. + Reset them to empty when garden is False. + """ + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = False + + + + # ------------------------------------------------------------ + # ACTIONS + # ------------------------------------------------------------ + + def action_property_sold(self): + if self.state == 'canceled': + raise UserError("You cannot sell a canceled property") + else: + self.state = 'sold' + + self.active = False + return True + + def action_property_cancel(self): + if self.state == 'sold': + raise UserError("You cannot cancel a sold property") + else: + self.state = 'canceled' + + self.active = False + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..98b71c3afb2 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,62 @@ +from odoo import api, fields, models +from datetime import datetime, timedelta + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + + price = fields.Float(string="Offer Price") + status = fields.Selection([ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], string="Offer Status", copy=False) + partner_id = fields.Many2one('res.partner', string="Partner", required=True) + property_id = fields.Many2one('estate.property', string="Property", required=True) + validity = fields.Integer(string="Validity (in days)", default=7) + date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_validity", store=True) + create_date = fields.Date(readonly=True, default=fields.Date.today) + + + + # ------------------------------------------------------------------------- + # COMPUTE METHODS + # ------------------------------------------------------------------------- + + @api.depends('validity') + def _compute_date_deadline(self): + for record in self: + if record.validity is not None: + record.date_deadline = datetime.now().date() + timedelta(days=record.validity) + else: + # If validity is not set, set the deadline to 7 days + record.date_deadline = datetime.now().date() + timedelta(days=7) + + def _inverse_validity(self): + for record in self: + if record.create_date and record.date_deadline: + record.validity = (record.date_deadline - record.create_date).days + + + + # ------------------------------------------------------------ + # ACTIONS + # ------------------------------------------------------------ + + def action_offer_accept(self): + for record in self: + record.status = "accepted" + record.property_id.state = "offer_accepted" + record.property_id.selling_price = record.price + record.property_id.buyer = record.partner_id + + other_offers = self.env['estate.property.offer'].search([ + ('property_id', '=', record.property_id.id), + ('id', '!=', record.id), + ('status', '!=', 'refused') + ]) + other_offers.write({'status': 'refused'}) + return True + + def action_offer_refuse(self): + self.status = "refused" + return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..ef4bd6c40ac --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tags" + + name = fields.Char(string="Tag Name") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..23539030fd9 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + + name = fields.Char(string="Type Name") diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4f716b0d7bf..46c5d7b8f02 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_user,access_estate_property_user,estate.model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,access_estate_property_type_user,estate.model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,access_estate_property_tag_user,estate.model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,access_estate_property_offer_user,estate.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 a10eeb571ab..296ee3f554f 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -8,5 +8,11 @@ + + + + + + diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml new file mode 100644 index 00000000000..928ad0c240c --- /dev/null +++ b/estate/views/estate_property_offers_views.xml @@ -0,0 +1,25 @@ + + + + + Property Offer + estate.property.offer + list,form + + + + estate.property.offer.list + estate.property.offer + + + + + + + +

+ + + + + + + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index d614943f681..cac24ef9c54 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,20 +4,24 @@ Estate estate.property list,form + {'search_default_availible_property': 1}
estate.property.list estate.property - + + + - + @@ -28,17 +32,18 @@
-

- + - + @@ -58,8 +63,8 @@ - - + + @@ -68,7 +73,7 @@ - + @@ -90,8 +95,12 @@ estate.property - + + + + + + From 6c496752c7bcb346f567ca8b0b8243817c59982c Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Wed, 12 Feb 2025 19:03:09 +0530 Subject: [PATCH 05/13] [IMP] estate: Add Inheritance and estate_account Used ondelete decorator for safe record deletions via Inheritance. Created estate account module to inherit estate property & generate invoices. --- estate/__manifest__.py | 4 +++- estate/models/__init__.py | 5 ++-- estate/models/estate_property.py | 16 +++++++++++-- estate/models/estate_property_offer.py | 20 ++++++++++++++++ estate/models/res_users.py | 7 ++++++ estate/views/estate_property_views.xml | 2 +- estate/views/estate_res_users_views.xml | 15 ++++++++++++ estate_account/__init__.py | 1 + estate_account/__manifest__.py | 11 +++++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 29 ++++++++++++++++++++++++ 11 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/estate_res_users_views.xml 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/__manifest__.py b/estate/__manifest__.py index ca0adfc51cc..de95dec917c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,13 +5,15 @@ 'depends': ['base'], 'author': 'Shiv Bhadaniya', 'application': True, + 'installable': True, 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_property_types_views.xml', 'views/estate_property_tags_views.xml', 'views/estate_property_offers_views.xml', + 'views/estate_property_types_views.xml', + 'views/estate_res_users_views.xml', 'views/estate_menus.xml', ] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..41393ab1e39 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,5 @@ from . import estate_property -from . import estate_property_type -from . import estate_property_tag from . import estate_property_offer +from . import estate_property_tag +from . import estate_property_type +from . import res_users \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4d4e7696df2..ff244dce046 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -37,7 +37,7 @@ class EstateProperty(models.Model): # Many2one relationship property_type_id = fields.Many2one('estate.property.type', string="Property Type") buyer = fields.Many2one('res.partner', string="Buyer", copy=False) - seller = fields.Many2one('res.users', string="Salesman", default=lambda self: self.env.user) + user_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) # Many2many relationship tag_ids = fields.Many2many('estate.property.tag', string="Property Tags") @@ -97,6 +97,7 @@ def _onchange_garden(self): # ------------------------------------------------------------ def action_property_sold(self): + print("This method is called from estate ---------------------------") if self.state == 'canceled': raise UserError("You cannot sell a canceled property") else: @@ -134,7 +135,7 @@ def action_property_cancel(self): # CONSTRAINTS METHODS # ------------------------------------------------------------------------- - @api.constrains('seeling_price', 'expected_price') + @api.constrains('selling_price', 'expected_price') def _check_selling_price(self): for record in self: if float_is_zero(record.selling_price, precision_digits=2): @@ -143,3 +144,14 @@ def _check_selling_price(self): minimum_price = record.expected_price * 0.9 if float_compare(record.selling_price, minimum_price, precision_digits=2) == -1: raise ValidationError(f"The selling price cannot be lower than 90% of the expected price.") + + + # ------------------------------------------------------------------------- + # CRUD METHODS + # ------------------------------------------------------------------------- + + @api.ondelete(at_uninstall=False) + def _unlink_if_not_new_or_cancelled(self): + for record in self: + if record.state not in ('new', 'canceled'): + raise UserError("You may only delete properties in state 'New' or 'Canceled'") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 12a920bfaa9..402b22bd92f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from odoo import api, fields, models from datetime import datetime, timedelta +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): _name = "estate.property.offer" @@ -70,3 +71,22 @@ def action_offer_refuse(self): _sql_constraints = [ ('check_price', 'CHECK(price > 0)', 'The price must be positive'), ] + + # ------------------------------------------------------------------------- + # CRUD METHODS + # ------------------------------------------------------------------------- + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + + offer_price = vals.get('price') + current_maximum_offer = self.search([('property_id', '=', vals['property_id'])], order="price desc", limit=1) # Fetch the current maximum offer for the property, already stored in the descending order of price. + + if offer_price < current_maximum_offer.price: + raise UserError(f"The offer price must be higher than {current_maximum_offer.price}") + else: + offer_reccived_property = self.env['estate.property'].browse(vals.get('property_id')) + offer_reccived_property.state = "offer_received" + + return super().create(vals_list) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..cd5454b1aab --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class ResUsers(models.Model): + _name = 'res.users' + _inherit = 'res.users' + + property_ids = fields.One2many('estate.property', 'user_id', string='Properties', domain = [("state" , "in" , ["new" , "offer_received"])]) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index cac24ef9c54..8a27d8eb91d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -79,7 +79,7 @@ - + diff --git a/estate/views/estate_res_users_views.xml b/estate/views/estate_res_users_views.xml new file mode 100644 index 00000000000..8854f841128 --- /dev/null +++ b/estate/views/estate_res_users_views.xml @@ -0,0 +1,15 @@ + + + + res.users.view.form.inherit.estate + res.users + + + + + + + + + + diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..e57bc0d6097 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,11 @@ +{ + 'name': 'estate_account', + 'description': 'Estate Account Module', + 'sequence': 1, + 'version': '1.0', + 'depends': ['estate', 'account'], + 'author': 'Shiv Bhadaniya', + "installable": True, + "application": True, + '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..be12d109b77 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,29 @@ +from odoo import Command, models + +class EstateAccount(models.Model): + _inherit = "estate.property" + + def action_property_sold(self): + self.env["account.move"].create( + { + "move_type": "out_invoice", + "partner_id": self.buyer.id, + "invoice_line_ids": [ + Command.create( + { + "name": "Property Sale", + "quantity": 1, + "price_unit": 1.06 * self.selling_price, + } + ), + Command.create( + { + "name": "Additional Charges", + "quantity": 1, + "price_unit": self.selling_price + 100, + } + ), + ], + } + ) + return super().action_property_sold() From c1271580051f06b75577074c5541535c13dc4281 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Thu, 13 Feb 2025 19:06:24 +0530 Subject: [PATCH 06/13] [IMP] estate: Implement Access Rights for Manager/Agent Introduced Kanban View for better visual representation of properties. Sets different access rights on different models. Added Manager and Agent access rights. Refactor the code for better quality. --- estate/__manifest__.py | 2 + estate/models/estate_property.py | 2 +- estate/models/estate_property_offer.py | 2 +- estate/security/estate_security.xml | 13 ++++ estate/security/ir.model.access.csv | 12 ++-- estate/views/estate_menus.xml | 12 ++-- estate/views/estate_property_tags_views.xml | 2 +- estate/views/estate_property_types_views.xml | 4 +- estate/views/estate_property_views.xml | 62 ++++++++++++++++---- 9 files changed, 83 insertions(+), 28 deletions(-) create mode 100644 estate/security/estate_security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index de95dec917c..7ebdc87f2cd 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,8 +7,10 @@ 'application': True, 'installable': True, 'license': 'LGPL-3', + 'category': 'Real Estate/Brokerage', 'data': [ 'security/ir.model.access.csv', + 'security/estate_security.xml', 'views/estate_property_views.xml', 'views/estate_property_tags_views.xml', 'views/estate_property_offers_views.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ff244dce046..3815407367f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import api, fields, models from datetime import datetime, timedelta +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 402b22bd92f..5eeada2c6b0 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,5 @@ -from odoo import api, fields, models from datetime import datetime, timedelta +from odoo import api, fields, models from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): diff --git a/estate/security/estate_security.xml b/estate/security/estate_security.xml new file mode 100644 index 00000000000..0f3c9eac76a --- /dev/null +++ b/estate/security/estate_security.xml @@ -0,0 +1,13 @@ + + + + Agent + + + + + Manager + + + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 46c5d7b8f02..fa9955316ba 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_user,access_estate_property_user,estate.model_estate_property,base.group_user,1,1,1,1 -access_estate_property_type_user,access_estate_property_type_user,estate.model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag_user,access_estate_property_tag_user,estate.model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer_user,access_estate_property_offer_user,estate.model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_manager,access_estate_property_manager,estate.model_estate_property,estate.estate_group_manager,1,1,1,0 +access_estate_property_type_manager,access_estate_property_type_manager,estate.model_estate_property_type,estate.estate_group_manager,1,1,1,0 +access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_estate_property_tag,estate.estate_group_manager,1,1,1,0 +access_estate_property_offer_manager,access_estate_property_offer_manager,estate.model_estate_property_offer,estate.estate_group_manager,1,1,1,0 +access_estate_property_type_agent,access_estate_property_type_agent,estate.model_estate_property_type,estate.estate_group_user,1,0,0,0 +access_estate_property_tag_agent,access_estate_property_tag_agent,estate.model_estate_property_tag,estate.estate_group_user,1,0,0,0 +access_estate_property_agent,access_estate_property_agent,estate.model_estate_property,estate.estate_group_user,1,1,1,0 +access_estate_property_offer_agent,access_estate_property_offer_agent,estate.model_estate_property_offer,estate.estate_group_user,1,1,1,0 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 14e7853a71a..3f352846945 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,17 +2,17 @@ - + - - + diff --git a/estate/views/estate_property_tags_views.xml b/estate/views/estate_property_tags_views.xml index 8ed8c574269..8c3e408897a 100644 --- a/estate/views/estate_property_tags_views.xml +++ b/estate/views/estate_property_tags_views.xml @@ -1,7 +1,7 @@ - + Property Tags estate.property.tag list,form diff --git a/estate/views/estate_property_types_views.xml b/estate/views/estate_property_types_views.xml index b0ee3505cd9..453b18e29a9 100644 --- a/estate/views/estate_property_types_views.xml +++ b/estate/views/estate_property_types_views.xml @@ -2,7 +2,7 @@ - + Property Types estate.property.type list,form @@ -21,7 +21,7 @@ - + estate.property.type.form estate.property.type diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8a27d8eb91d..5b27342b9b8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,10 +1,10 @@ - + Estate estate.property - list,form - {'search_default_availible_property': 1} + list,form,kanban + {'search_default_availible_property': 1} @@ -21,7 +21,7 @@ - + @@ -32,15 +32,19 @@
-

- + @@ -50,7 +54,7 @@ - + @@ -67,13 +71,14 @@ - + - + @@ -90,15 +95,46 @@
+ + estate.property.kanban + estate.property + + + + + +
+ +
+
Expected Price: +
+
Best Price: +
+
Selling Price: +
+
+ +
+
+
+
+
+
+ estate.property.search estate.property - + - + From aa466868fef0e4e8b85f2d45e335c5f340f2355a Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Fri, 14 Feb 2025 18:59:44 +0530 Subject: [PATCH 07/13] [IMP] estate: Improve data management, access Implemented Record Rules for data visibility control. Used security bypass where necessary. Programmatically checked security permissions to ensure proper access. Added demo data for structured testing. Extended models using inheritance for modularity. Added X2many fields to enhance relational data handling. Enabled chatter functionality for improved communication and tracking. --- estate/__manifest__.py | 7 ++- estate/data/estate.property.type.csv | 5 ++ estate/demo/estate_property_demo.xml | 55 +++++++++++++++++++++ estate/demo/estate_property_demo_offers.xml | 27 ++++++++++ estate/models/estate_property.py | 5 +- estate/models/estate_property_offer.py | 2 +- estate/security/estate_security.xml | 16 ++++++ estate/security/ir.model.access.csv | 6 +-- estate/views/estate_menus.xml | 2 +- estate/views/estate_property_views.xml | 2 +- estate_account/models/estate_property.py | 7 ++- 11 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 estate/data/estate.property.type.csv create mode 100644 estate/demo/estate_property_demo.xml create mode 100644 estate/demo/estate_property_demo_offers.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7ebdc87f2cd..b978dccfa51 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,11 +11,16 @@ 'data': [ 'security/ir.model.access.csv', 'security/estate_security.xml', + 'data/estate.property.type.csv', 'views/estate_property_views.xml', 'views/estate_property_tags_views.xml', 'views/estate_property_offers_views.xml', 'views/estate_property_types_views.xml', 'views/estate_res_users_views.xml', + 'demo/estate_property_demo.xml', 'views/estate_menus.xml', - ] + ], + 'demo': [ + 'demo/estate_property_demo.xml', + ], } diff --git a/estate/data/estate.property.type.csv b/estate/data/estate.property.type.csv new file mode 100644 index 00000000000..15fab584b56 --- /dev/null +++ b/estate/data/estate.property.type.csv @@ -0,0 +1,5 @@ +"id","name" +estate_property_type_1,"Residential" +estate_property_type_2,"Commercial" +estate_property_type_3,"Industrial", +estate_property_type_4,"Land" diff --git a/estate/demo/estate_property_demo.xml b/estate/demo/estate_property_demo.xml new file mode 100644 index 00000000000..97f6aa01f50 --- /dev/null +++ b/estate/demo/estate_property_demo.xml @@ -0,0 +1,55 @@ + + + + + + Big Villa + A nice and big villa + 12345 + new + True + 10 + 6 + 100 + 4 + True + True + 100000 + north + + + + + Trailer home + Home in a tariler park + 54321 + True + new + 10 + 1 + 10 + 4 + False + True + 3 + south + + + + diff --git a/estate/demo/estate_property_demo_offers.xml b/estate/demo/estate_property_demo_offers.xml new file mode 100644 index 00000000000..6116ea3b036 --- /dev/null +++ b/estate/demo/estate_property_demo_offers.xml @@ -0,0 +1,27 @@ + + + + + + + 10000 + 14 + + + + + + + 1500000 + 14 + + + + + + + 1500001 + 14 + + + \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3815407367f..7dc5d9176a8 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -36,7 +36,7 @@ class EstateProperty(models.Model): # Many2one relationship property_type_id = fields.Many2one('estate.property.type', string="Property Type") - buyer = fields.Many2one('res.partner', string="Buyer", copy=False) + buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) user_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) # Many2many relationship @@ -52,7 +52,7 @@ class EstateProperty(models.Model): _order = "id desc" - + company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id) # ------------------------------------------------------------------------- # COMPUTE METHODS @@ -97,7 +97,6 @@ def _onchange_garden(self): # ------------------------------------------------------------ def action_property_sold(self): - print("This method is called from estate ---------------------------") if self.state == 'canceled': raise UserError("You cannot sell a canceled property") else: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5eeada2c6b0..319ac1ea28b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -49,7 +49,7 @@ def action_offer_accept(self): record.status = "accepted" record.property_id.state = "offer_accepted" record.property_id.selling_price = record.price - record.property_id.buyer = record.partner_id + record.property_id.buyer_id = record.partner_id other_offers = self.env['estate.property.offer'].search([ ('property_id', '=', record.property_id.id), diff --git a/estate/security/estate_security.xml b/estate/security/estate_security.xml index 0f3c9eac76a..a4f944dbf41 100644 --- a/estate/security/estate_security.xml +++ b/estate/security/estate_security.xml @@ -10,4 +10,20 @@ + + + + + + ['|', ('user_id', '=', user.id), ('user_id', '=', False),] + + + + + + + + + +
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index fa9955316ba..de01ae2fcc4 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,8 +1,8 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property_manager,access_estate_property_manager,estate.model_estate_property,estate.estate_group_manager,1,1,1,0 -access_estate_property_type_manager,access_estate_property_type_manager,estate.model_estate_property_type,estate.estate_group_manager,1,1,1,0 -access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_estate_property_tag,estate.estate_group_manager,1,1,1,0 -access_estate_property_offer_manager,access_estate_property_offer_manager,estate.model_estate_property_offer,estate.estate_group_manager,1,1,1,0 +access_estate_property_type_manager,access_estate_property_type_manager,estate.model_estate_property_type,estate.estate_group_manager,1,1,1,1 +access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_estate_property_tag,estate.estate_group_manager,1,1,1,1 +access_estate_property_offer_manager,access_estate_property_offer_manager,estate.model_estate_property_offer,estate.estate_group_manager,1,1,1,1 access_estate_property_type_agent,access_estate_property_type_agent,estate.model_estate_property_type,estate.estate_group_user,1,0,0,0 access_estate_property_tag_agent,access_estate_property_tag_agent,estate.model_estate_property_tag,estate.estate_group_user,1,0,0,0 access_estate_property_agent,access_estate_property_agent,estate.model_estate_property,estate.estate_group_user,1,1,1,0 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 3f352846945..8cee18839dd 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -10,7 +10,7 @@ - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5b27342b9b8..ccd57a04080 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -85,7 +85,7 @@ - + diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index be12d109b77..0ba4bfa3468 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -4,10 +4,13 @@ class EstateAccount(models.Model): _inherit = "estate.property" def action_property_sold(self): - self.env["account.move"].create( + + self.check_access('write') + + self.env["account.move"].sudo().create( { "move_type": "out_invoice", - "partner_id": self.buyer.id, + "partner_id": self.buyer_id.id, "invoice_line_ids": [ Command.create( { From 694781f61e3c6f64491ba701fb8203c3c6475668 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Mon, 17 Feb 2025 18:52:44 +0530 Subject: [PATCH 08/13] [IMP] estate: Add Chatter, PDF Reports and Controller Enabled Chatter functionality for improved communication and tracking. Added a property offers report in the Print menu. Improved reports with conditional logic using t-if/t-else. Created a new report for res.users to list their properties and offers. Implemented basic controller functionality. --- estate/__init__.py | 1 + estate/__manifest__.py | 9 +++- estate/controllers/__init__.py | 1 + .../controllers/estate_available_property.py | 14 ++++++ estate/models/estate_property.py | 8 ++-- estate/models/estate_property_offer.py | 2 +- estate/models/estate_property_tag.py | 4 +- estate/models/estate_property_type.py | 3 +- ...state_property_offer_res_user_template.xml | 38 ++++++++++++++++ .../estate_property_offer_subtemplate.xml | 45 +++++++++++++++++++ estate/report/estate_property_reports.xml | 25 +++++++++++ estate/report/estate_property_templates.xml | 27 +++++++++++ estate/security/ir.model.access.csv | 1 + .../views/estate_available_propery_view.xml | 10 +++++ estate/views/estate_property_views.xml | 2 + estate_account/__manifest__.py | 3 ++ .../report/estate_property_inherit.xml | 17 +++++++ 17 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 estate/controllers/__init__.py create mode 100644 estate/controllers/estate_available_property.py create mode 100644 estate/report/estate_property_offer_res_user_template.xml create mode 100644 estate/report/estate_property_offer_subtemplate.xml create mode 100644 estate/report/estate_property_reports.xml create mode 100644 estate/report/estate_property_templates.xml create mode 100644 estate/views/estate_available_propery_view.xml create mode 100644 estate_account/report/estate_property_inherit.xml diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..38718f0843b 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ from . import models +from . import controllers \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b978dccfa51..fc16716b9b8 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,17 +2,22 @@ 'name': 'Estate', 'description': 'Estate Module', 'version': '1.0', - 'depends': ['base'], + 'depends': ['base','mail', 'website'], 'author': 'Shiv Bhadaniya', 'application': True, 'installable': True, 'license': 'LGPL-3', 'category': 'Real Estate/Brokerage', 'data': [ - 'security/ir.model.access.csv', 'security/estate_security.xml', + 'security/ir.model.access.csv', 'data/estate.property.type.csv', 'views/estate_property_views.xml', + 'report/estate_property_offer_subtemplate.xml', + 'report/estate_property_templates.xml', + 'report/estate_property_offer_res_user_template.xml', + 'report/estate_property_reports.xml', + 'views/estate_available_propery_view.xml', 'views/estate_property_tags_views.xml', 'views/estate_property_offers_views.xml', 'views/estate_property_types_views.xml', diff --git a/estate/controllers/__init__.py b/estate/controllers/__init__.py new file mode 100644 index 00000000000..59d782585f3 --- /dev/null +++ b/estate/controllers/__init__.py @@ -0,0 +1 @@ +from . import estate_available_property \ No newline at end of file diff --git a/estate/controllers/estate_available_property.py b/estate/controllers/estate_available_property.py new file mode 100644 index 00000000000..0095ad3a25d --- /dev/null +++ b/estate/controllers/estate_available_property.py @@ -0,0 +1,14 @@ +from odoo import http +from odoo.http import request + +class EstateAvailableProperty(http.Controller): + + @http.route('/estate/available_property', type='http', auth='public', methods=['GET'], website=True) + def available_property(self, **kw): + properties = request.env['estate.property'].search([ + ('state', 'in', ['new', 'offer_received']) + ]) + + return request.render('estate.available_property', { + 'properties': properties + }) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7dc5d9176a8..89f1eda9e6a 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -6,8 +6,10 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Model" + _inherit = ["mail.thread"] + _order = "id desc" - name = fields.Char(required=True) + name = fields.Char(required=True, tracking=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date(copy=False, default= datetime.now() + timedelta(days=90)) @@ -32,7 +34,7 @@ class EstateProperty(models.Model): ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('canceled', 'Canceled'), - ], copy=False, default='new', required=True,) + ], copy=False, default='new', required=True, tracking=True) # Many2one relationship property_type_id = fields.Many2one('estate.property.type', string="Property Type") @@ -50,8 +52,6 @@ class EstateProperty(models.Model): best_price = fields.Float(compute='_compute_best_price', store=True) - _order = "id desc" - company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.user.company_id) # ------------------------------------------------------------------------- diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 319ac1ea28b..4798c77b206 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -6,6 +6,7 @@ class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Estate Property Offer" + _order = "price desc" price = fields.Float(string="Offer Price") status = fields.Selection([ ('accepted', 'Accepted'), @@ -16,7 +17,6 @@ class EstatePropertyOffer(models.Model): validity = fields.Integer(string="Validity (in days)", default=7) date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_validity", store=True) create_date = fields.Date(readonly=True, default=fields.Date.today) - _order = "price desc" property_type_id = fields.Many2one('estate.property.type', string="Property Type", related='property_id.property_type_id', store=True) # related field: Automatically fetches the property type. diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index b592d88759a..c25ec2e1da0 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -4,10 +4,10 @@ class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Estate Property Tags" - name = fields.Char(string="Tag Name") - _order = "name" + name = fields.Char(string="Tag Name") + color = fields.Integer() # ------------------------------------------------------------------------- diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 56f1d1cec7d..5a5365627fc 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -3,12 +3,11 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Estate Property Type" + _order = "name" name = fields.Char(string="Type Name") property_ids = fields.One2many('estate.property', 'property_type_id', string="Properties") - _order = "name" - offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string="Offers") offer_count = fields.Integer(compute='_compute_offer_count') sequence = fields.Integer() diff --git a/estate/report/estate_property_offer_res_user_template.xml b/estate/report/estate_property_offer_res_user_template.xml new file mode 100644 index 00000000000..e83a7da6500 --- /dev/null +++ b/estate/report/estate_property_offer_res_user_template.xml @@ -0,0 +1,38 @@ + + + + + + diff --git a/estate/report/estate_property_offer_subtemplate.xml b/estate/report/estate_property_offer_subtemplate.xml new file mode 100644 index 00000000000..ffa1f8e3f22 --- /dev/null +++ b/estate/report/estate_property_offer_subtemplate.xml @@ -0,0 +1,45 @@ + + + + diff --git a/estate/report/estate_property_reports.xml b/estate/report/estate_property_reports.xml new file mode 100644 index 00000000000..5131a111b6d --- /dev/null +++ b/estate/report/estate_property_reports.xml @@ -0,0 +1,25 @@ + + + + + Print + estate.property + estate.report_property_offers + estate.report_property_offers + '%s_offers' % (object.name or 'Property') + + report + qweb-pdf + + + + + Report + res.users + estate.estate_property_offer_res_user_template + estate.estate_property_offer_res_user_template + + report + qweb-pdf + + diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml new file mode 100644 index 00000000000..c820f5a0a22 --- /dev/null +++ b/estate/report/estate_property_templates.xml @@ -0,0 +1,27 @@ + + + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index de01ae2fcc4..4760a7fcb6b 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,4 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 access_estate_property_manager,access_estate_property_manager,estate.model_estate_property,estate.estate_group_manager,1,1,1,0 access_estate_property_type_manager,access_estate_property_type_manager,estate.model_estate_property_type,estate.estate_group_manager,1,1,1,1 access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_estate_property_tag,estate.estate_group_manager,1,1,1,1 diff --git a/estate/views/estate_available_propery_view.xml b/estate/views/estate_available_propery_view.xml new file mode 100644 index 00000000000..ddd8f188b97 --- /dev/null +++ b/estate/views/estate_available_propery_view.xml @@ -0,0 +1,10 @@ + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ccd57a04080..ee4f2005eb0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -91,6 +91,8 @@
+ +
diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index e57bc0d6097..29cf8d5dcc6 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -8,4 +8,7 @@ "installable": True, "application": True, 'license': 'LGPL-3', + 'data': [ + 'report/estate_property_inherit.xml', + ] } diff --git a/estate_account/report/estate_property_inherit.xml b/estate_account/report/estate_property_inherit.xml new file mode 100644 index 00000000000..bd3712546a2 --- /dev/null +++ b/estate_account/report/estate_property_inherit.xml @@ -0,0 +1,17 @@ + + + + + + + From b040d6e415472a332ccc0e82b49bc9a2fa8ab52d Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Tue, 18 Feb 2025 18:47:36 +0530 Subject: [PATCH 09/13] [IMP] estate: Add Controllers, Wizards, and Test Cases Displayed property listings on the website using web controllers. Added a date-picker for enhanced user interaction. Implemented wizards for easier data entry and an improved user experience. Add test cases for sold properties without offers, and vice versa. Fix minor typos. --- estate/__init__.py | 4 +- estate/__manifest__.py | 4 +- .../controllers/estate_available_property.py | 52 +++- estate/demo/estate_property_demo.xml | 223 +++++++++++++++++- estate/demo/estate_property_website.xml | 9 + estate/models/estate_property.py | 10 + estate/models/estate_property_offer.py | 11 +- estate/security/ir.model.access.csv | 1 + estate/tests/__init__.py | 1 + estate/tests/test_property_sold.py | 18 ++ .../views/estate_available_property_view.xml | 120 ++++++++++ .../views/estate_available_propery_view.xml | 10 - estate/views/estate_property_views.xml | 16 +- estate/wizard/__init__.py | 1 + estate/wizard/estate_property_offer_wizard.py | 34 +++ .../estate_property_offer_wizard_views.xml | 29 +++ 16 files changed, 515 insertions(+), 28 deletions(-) create mode 100644 estate/demo/estate_property_website.xml create mode 100644 estate/tests/__init__.py create mode 100644 estate/tests/test_property_sold.py create mode 100644 estate/views/estate_available_property_view.xml delete mode 100644 estate/views/estate_available_propery_view.xml create mode 100644 estate/wizard/__init__.py create mode 100644 estate/wizard/estate_property_offer_wizard.py create mode 100644 estate/wizard/estate_property_offer_wizard_views.xml diff --git a/estate/__init__.py b/estate/__init__.py index 38718f0843b..720587b971a 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1,4 @@ from . import models -from . import controllers \ No newline at end of file +from . import controllers +from . import tests +from . import wizard diff --git a/estate/__manifest__.py b/estate/__manifest__.py index fc16716b9b8..377e313d96b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,17 +12,19 @@ 'security/estate_security.xml', 'security/ir.model.access.csv', 'data/estate.property.type.csv', + 'wizard/estate_property_offer_wizard_views.xml', 'views/estate_property_views.xml', 'report/estate_property_offer_subtemplate.xml', 'report/estate_property_templates.xml', 'report/estate_property_offer_res_user_template.xml', 'report/estate_property_reports.xml', - 'views/estate_available_propery_view.xml', + 'views/estate_available_property_view.xml', 'views/estate_property_tags_views.xml', 'views/estate_property_offers_views.xml', 'views/estate_property_types_views.xml', 'views/estate_res_users_views.xml', 'demo/estate_property_demo.xml', + 'demo/estate_property_website.xml', 'views/estate_menus.xml', ], 'demo': [ diff --git a/estate/controllers/estate_available_property.py b/estate/controllers/estate_available_property.py index 0095ad3a25d..70d0f5e7f86 100644 --- a/estate/controllers/estate_available_property.py +++ b/estate/controllers/estate_available_property.py @@ -3,12 +3,48 @@ class EstateAvailableProperty(http.Controller): - @http.route('/estate/available_property', type='http', auth='public', methods=['GET'], website=True) - def available_property(self, **kw): - properties = request.env['estate.property'].search([ - ('state', 'in', ['new', 'offer_received']) - ]) - - return request.render('estate.available_property', { - 'properties': properties + @http.route(['/estate/available_property', '/estate/available_property/page/'], type='http', auth='public', methods=['GET'], website=True) + def available_property(self, page=1, **kw): + """ + Render a list of available properties with pagination. + """ + Property = request.env['estate.property'] + + domain = [('state', 'in', ['new', 'offer_received'])] + listed_after = kw.get('listed_after') + if listed_after: + domain.append(('create_date', '>=', listed_after)) + + property_count = Property.search_count(domain) + url_args = {'listed_after': listed_after} if listed_after else {} + + pager = request.website.pager( + url="/estate/available_property", + total=property_count, + page=page, + step=6, + url_args=url_args + ) + + properties = Property.search(domain, limit=6, offset=pager['offset']) + + return request.render('estate.available_property_listing', { + 'properties': properties, + 'page_name': 'properties', + 'default_url': '/estate/available_property', + 'pager': pager, + 'listed_after': listed_after or False, + }) + + + @http.route('/estate/available_property_details/', type='http', auth='public', website=True) + def property_details(self, id, **kw): + Property = request.env['estate.property'] + property = Property.browse(id) + + if not property: + return request.not_found() + + return request.render('estate.available_property_details', { + 'property': property, }) diff --git a/estate/demo/estate_property_demo.xml b/estate/demo/estate_property_demo.xml index 97f6aa01f50..245eef85359 100644 --- a/estate/demo/estate_property_demo.xml +++ b/estate/demo/estate_property_demo.xml @@ -19,6 +19,227 @@ + + Luxury Mansion + An elegant and luxurious mansion + 54321 + new + True + 25 + 8 + 250 + 6 + True + True + 50000 + south + + + + + Modern Apartment + A stylish and modern apartment + 67890 + new + True + 15 + 3 + 90 + 2 + False + False + 0 + east + + + + + Countryside Cottage + A cozy cottage in the countryside + 13579 + new + True + 8 + 4 + 120 + 3 + True + True + 20000 + west + + + + + Skyline Penthouse + A penthouse with a breathtaking skyline view + 24680 + new + True + 30 + 5 + 180 + 2 + False + False + 0 + north + + + + + Seaside Bungalow + A relaxing bungalow by the sea + 11223 + new + True + 12 + 4 + 110 + 3 + True + True + 15000 + south + + + + + Mountain Retreat + A peaceful retreat in the mountains + 33445 + new + True + 20 + 6 + 160 + 4 + True + True + 40000 + west + + + + + Lake House + A beautiful house by the lake + 55667 + new + True + 18 + 5 + 140 + 3 + True + True + 30000 + east + + + + + Urban Loft + A modern loft in the city + 66778 + new + True + 22 + 2 + 85 + 1 + False + False + 0 + north + + + + + Desert Villa + A luxurious villa in the desert + 77889 + new + True + 35 + 7 + 200 + 5 + True + True + 70000 + south + + + + + Eco Cabin + A sustainable eco-friendly cabin + 88990 + new + True + 14 + 3 + 95 + 2 + False + True + 25000 + west + + + + + Cliffside Retreat + A serene retreat on the cliffs + 99001 + new + True + 28 + 4 + 150 + 3 + True + True + 35000 + north + + + + + Seaside Bungalow + A cozy bungalow by the sea + 10101 + new + True + 20 + 3 + 120 + 2 + True + True + 28000 + east + + + + + Hilltop Mansion + A grand mansion on the hills + 20202 + new + True + 50 + 8 + 300 + 6 + True + True + 80000 + south + + + Trailer home Home in a tariler park @@ -34,7 +255,7 @@ 3 south - + + + + Properties + /estate/available_property + + + diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 89f1eda9e6a..8a19571d904 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -35,6 +35,7 @@ class EstateProperty(models.Model): ('sold', 'Sold'), ('canceled', 'Canceled'), ], copy=False, default='new', required=True, tracking=True) + image = fields.Image(string="Property Image") # Many2one relationship property_type_id = fields.Many2one('estate.property.type', string="Property Type") @@ -102,6 +103,15 @@ def action_property_sold(self): else: self.state = 'sold' + offers = self.offer_ids + any_offer_accept = False + for offer in offers: + if offer.status == 'accepted': + any_offer_accept = True + continue + if any_offer_accept == False: + raise ValidationError("At least one offer must be accepted before selling the property.") + self.active = False return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4798c77b206..c29fa66344e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -80,13 +80,20 @@ def action_offer_refuse(self): def create(self, vals_list): for vals in vals_list: + # Sold Property can't make a new offer + property = self.env['estate.property'].browse(vals.get('property_id')) + if not property.exists(): + raise UserError("The property you are referring to doesn't exist.") + elif property.state == 'sold': + raise UserError("You cannot create an offer for a sold property.") + offer_price = vals.get('price') current_maximum_offer = self.search([('property_id', '=', vals['property_id'])], order="price desc", limit=1) # Fetch the current maximum offer for the property, already stored in the descending order of price. if offer_price < current_maximum_offer.price: raise UserError(f"The offer price must be higher than {current_maximum_offer.price}") else: - offer_reccived_property = self.env['estate.property'].browse(vals.get('property_id')) - offer_reccived_property.state = "offer_received" + offer_received_property = self.env['estate.property'].browse(vals.get('property_id')) + offer_received_property.state = "offer_received" return super().create(vals_list) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4760a7fcb6b..7c2482dd165 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +access_estate_property_offer_wizard,access_estate_property_offer_wizard,estate.model_estate_property_offer_wizard,base.group_user,1,1,1,1 access_estate_property_manager,access_estate_property_manager,estate.model_estate_property,estate.estate_group_manager,1,1,1,0 access_estate_property_type_manager,access_estate_property_type_manager,estate.model_estate_property_type,estate.estate_group_manager,1,1,1,1 access_estate_property_tag_manager,access_estate_property_tag_manager,estate.model_estate_property_tag,estate.estate_group_manager,1,1,1,1 diff --git a/estate/tests/__init__.py b/estate/tests/__init__.py new file mode 100644 index 00000000000..a42df2e12ba --- /dev/null +++ b/estate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_property_sold diff --git a/estate/tests/test_property_sold.py b/estate/tests/test_property_sold.py new file mode 100644 index 00000000000..15c265090b9 --- /dev/null +++ b/estate/tests/test_property_sold.py @@ -0,0 +1,18 @@ +from odoo.tests.common import TransactionCase +from odoo.exceptions import ValidationError +from odoo.tests import tagged + +@tagged('post_install', '-at_install') +class TestEstateProperty(TransactionCase): + + def test_sell_property_without_accepted_offer(self): + estate_property = self.env['estate.property'] + + property = estate_property.create({ + "name": "Test Property Without Offer", + "expected_price": "100", + "state": "new", + }) + + with self.assertRaises(ValidationError): + property.action_property_sold() diff --git a/estate/views/estate_available_property_view.xml b/estate/views/estate_available_property_view.xml new file mode 100644 index 00000000000..89ff3d97249 --- /dev/null +++ b/estate/views/estate_available_property_view.xml @@ -0,0 +1,120 @@ + + + + + + + + diff --git a/estate/views/estate_available_propery_view.xml b/estate/views/estate_available_propery_view.xml deleted file mode 100644 index ddd8f188b97..00000000000 --- a/estate/views/estate_available_propery_view.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ee4f2005eb0..5d944c23009 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ Estate estate.property list,form,kanban - {'search_default_availible_property': 1} + {'search_default_available_property': 1} @@ -22,11 +22,15 @@ +
+
- + estate.property.form estate.property @@ -52,6 +56,7 @@ + @@ -92,7 +97,7 @@ - + @@ -101,7 +106,8 @@ estate.property.kanban estate.property - + @@ -131,7 +137,7 @@ estate.property - diff --git a/estate/wizard/__init__.py b/estate/wizard/__init__.py new file mode 100644 index 00000000000..e9926bcd3ec --- /dev/null +++ b/estate/wizard/__init__.py @@ -0,0 +1 @@ +from . import estate_property_offer_wizard diff --git a/estate/wizard/estate_property_offer_wizard.py b/estate/wizard/estate_property_offer_wizard.py new file mode 100644 index 00000000000..cb8cedb209c --- /dev/null +++ b/estate/wizard/estate_property_offer_wizard.py @@ -0,0 +1,34 @@ +from datetime import timedelta + +from odoo import fields, models +from odoo.exceptions import UserError + +class EstatePropertyOfferWizard(models.TransientModel): + _name = "estate.property.offer.wizard" + _description = "Estate Property Offer Wizard" + + price = fields.Float(string="Price", required=True) + buyer_id = fields.Many2one("res.partner", string="Partner", required=True) + date_deadline = fields.Date("Deadline", default=lambda self: fields.Date.today() + timedelta(days=7), required=True) + + + + def add_offers_to_multiple_properties(self): + selected_properties = self.env['estate.property'].browse(self.env.context.get('active_ids', [])) + + offer_price = self.price + offer_buyer_id = self.buyer_id + offer_date_deadline = self.date_deadline + + for property in selected_properties: + + if property.state == 'sold': + raise UserError(f"The property '{property.name}' is alredy sold ") + + self.env['estate.property.offer'].create({ + 'price' : offer_price, + 'partner_id' : offer_buyer_id.id, + 'date_deadline':offer_date_deadline, + 'property_id' : property.id + }) + return {'type': 'ir.actions.act_window_close'} diff --git a/estate/wizard/estate_property_offer_wizard_views.xml b/estate/wizard/estate_property_offer_wizard_views.xml new file mode 100644 index 00000000000..8773fa7b90d --- /dev/null +++ b/estate/wizard/estate_property_offer_wizard_views.xml @@ -0,0 +1,29 @@ + + + + estate.property.offer.wizard.form + estate.property.offer.wizard + +
+ + + + + +
+
+
+
+
+ + + Estate Property Offer Wizard + estate.property.offer.wizard + form + new + + + +
From 8dd5ac3032d2dcae7410ed9696f75ada7ac01c10 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Wed, 19 Feb 2025 18:47:27 +0530 Subject: [PATCH 10/13] [IMP] awesomeowl_owl: Start OWL Framework & Implement Components Built a counter using useState with an increment method. Extracted Counter into a separate component and reused it. Created a Card component with prop validation. Started work on the Todo list. --- awesome_owl/static/src/components/card/card.js | 10 ++++++++++ .../static/src/components/card/card.xml | 15 +++++++++++++++ .../static/src/components/counter/counter.js | 18 ++++++++++++++++++ .../static/src/components/counter/counter.xml | 11 +++++++++++ .../static/src/components/todo/todo_list.js | 12 ++++++++++++ awesome_owl/static/src/playground.js | 18 ++++++++++++++++-- awesome_owl/static/src/playground.xml | 14 +++++++++----- 7 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 awesome_owl/static/src/components/card/card.js create mode 100644 awesome_owl/static/src/components/card/card.xml create mode 100644 awesome_owl/static/src/components/counter/counter.js create mode 100644 awesome_owl/static/src/components/counter/counter.xml create mode 100644 awesome_owl/static/src/components/todo/todo_list.js diff --git a/awesome_owl/static/src/components/card/card.js b/awesome_owl/static/src/components/card/card.js new file mode 100644 index 00000000000..4f10f8acc4b --- /dev/null +++ b/awesome_owl/static/src/components/card/card.js @@ -0,0 +1,10 @@ +import { Component } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + + static props = { + title: { type: String, optional: true }, + content: { type: String, optional: true }, + }; +} diff --git a/awesome_owl/static/src/components/card/card.xml b/awesome_owl/static/src/components/card/card.xml new file mode 100644 index 00000000000..579a87f123c --- /dev/null +++ b/awesome_owl/static/src/components/card/card.xml @@ -0,0 +1,15 @@ + + + +
+
+
+ +
+

+ +

+
+
+
+
diff --git a/awesome_owl/static/src/components/counter/counter.js b/awesome_owl/static/src/components/counter/counter.js new file mode 100644 index 00000000000..dba33f73f5d --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.js @@ -0,0 +1,18 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + + static props = { + callbackIncrement: { type: Function }, + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value += 1; + this.props.callbackIncrement(); + } +} diff --git a/awesome_owl/static/src/components/counter/counter.xml b/awesome_owl/static/src/components/counter/counter.xml new file mode 100644 index 00000000000..2c450f829c0 --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.xml @@ -0,0 +1,11 @@ + + + +
+

+ +

+ +
+
+
diff --git a/awesome_owl/static/src/components/todo/todo_list.js b/awesome_owl/static/src/components/todo/todo_list.js new file mode 100644 index 00000000000..33721c76573 --- /dev/null +++ b/awesome_owl/static/src/components/todo/todo_list.js @@ -0,0 +1,12 @@ +import { Component, useState } from "@odoo/owl"; + +export class Todo extends Component { + static template = "awesome_owl.todo_list"; + static props = {}; + + setup() { + this.todos = useState([ + { id: 1, description: "Buy milk", isCompleted: false }, + ]); + } +} diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..67c67a5a348 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,21 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import { Counter } from "./components/counter/counter"; +import { Card } from "./components/card/card"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.playground"; + static components = { Counter, Card }; + static props = {}; + + setup() { + this.state = useState({ sum: 0 }); + } + + card1ContentValue = markup("
Some Content
"); + + incrementSum() { + this.state.sum += 1; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..b89639b4e66 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,14 @@ - + - -
- hello world +
+ + +

Total Sum: + +

+ +
- From 0106c7dc60e3d44e01830348a1f82ab2a595c6b4 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Thu, 20 Feb 2025 17:10:39 +0530 Subject: [PATCH 11/13] [IMP] awesomeowl_owl: Implement To-Do List using Hooks - Added a To-Do List component using `useState` and `useRef` - Improved understanding of the component lifecycle and Hooks - Utilized slots for better component structure --- .../static/src/components/card/card.js | 17 ++++++- .../static/src/components/card/card.xml | 13 +++-- .../static/src/components/counter/counter.js | 6 ++- .../static/src/components/counter/counter.xml | 14 +++--- .../static/src/components/todo/todo_item.js | 19 ++++++++ .../static/src/components/todo/todo_item.xml | 21 +++++++++ .../static/src/components/todo/todo_list.js | 47 +++++++++++++++++-- .../static/src/components/todo/todo_list.xml | 17 +++++++ awesome_owl/static/src/playground.js | 5 +- awesome_owl/static/src/playground.xml | 38 +++++++++++---- awesome_owl/static/src/utils.js | 15 ++++++ 11 files changed, 182 insertions(+), 30 deletions(-) create mode 100644 awesome_owl/static/src/components/todo/todo_item.js create mode 100644 awesome_owl/static/src/components/todo/todo_item.xml create mode 100644 awesome_owl/static/src/components/todo/todo_list.xml create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/components/card/card.js b/awesome_owl/static/src/components/card/card.js index 4f10f8acc4b..3e85928e555 100644 --- a/awesome_owl/static/src/components/card/card.js +++ b/awesome_owl/static/src/components/card/card.js @@ -1,10 +1,23 @@ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; export class Card extends Component { static template = "awesome_owl.card"; static props = { title: { type: String, optional: true }, - content: { type: String, optional: true }, + slots: { + type: Object, + optional: true, + }, }; + + setup() { + this.state = useState({ + isCounterOpen: false, + }); + } + + toggleCounter() { + this.state.isCounterOpen = !this.state.isCounterOpen; + } } diff --git a/awesome_owl/static/src/components/card/card.xml b/awesome_owl/static/src/components/card/card.xml index 579a87f123c..c1816078162 100644 --- a/awesome_owl/static/src/components/card/card.xml +++ b/awesome_owl/static/src/components/card/card.xml @@ -1,14 +1,17 @@ -
+
-
+
-

- -

+ +
+ +
diff --git a/awesome_owl/static/src/components/counter/counter.js b/awesome_owl/static/src/components/counter/counter.js index dba33f73f5d..1bb8a03a4cf 100644 --- a/awesome_owl/static/src/components/counter/counter.js +++ b/awesome_owl/static/src/components/counter/counter.js @@ -4,7 +4,7 @@ export class Counter extends Component { static template = "awesome_owl.counter"; static props = { - callbackIncrement: { type: Function }, + callbackIncrement: { type: Function, optional: true }, }; setup() { @@ -13,6 +13,8 @@ export class Counter extends Component { increment() { this.state.value += 1; - this.props.callbackIncrement(); + if (this.props.callbackIncrement) { + this.props.callbackIncrement(); + } } } diff --git a/awesome_owl/static/src/components/counter/counter.xml b/awesome_owl/static/src/components/counter/counter.xml index 2c450f829c0..68c4ec1f15f 100644 --- a/awesome_owl/static/src/components/counter/counter.xml +++ b/awesome_owl/static/src/components/counter/counter.xml @@ -1,11 +1,13 @@ -
-

- -

- -
+ +
+

+ +

+ +
+
diff --git a/awesome_owl/static/src/components/todo/todo_item.js b/awesome_owl/static/src/components/todo/todo_item.js new file mode 100644 index 00000000000..b478e66bbfc --- /dev/null +++ b/awesome_owl/static/src/components/todo/todo_item.js @@ -0,0 +1,19 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + + static props = { + todo_item: { type: Object }, + callbackToggleState: { type: Function }, + callbackRemoveTodo: { type: Function }, + }; + + removeTodo = (removeTodoId) => { + this.props.callbackRemoveTodo(removeTodoId); + }; + + toggleState = (todoId) => { + this.props.callbackToggleState(todoId); + }; +} diff --git a/awesome_owl/static/src/components/todo/todo_item.xml b/awesome_owl/static/src/components/todo/todo_item.xml new file mode 100644 index 00000000000..8a922365afc --- /dev/null +++ b/awesome_owl/static/src/components/todo/todo_item.xml @@ -0,0 +1,21 @@ + + + +
+
+ + + . + + + + +
+ +
+
+
diff --git a/awesome_owl/static/src/components/todo/todo_list.js b/awesome_owl/static/src/components/todo/todo_list.js index 33721c76573..84d57842609 100644 --- a/awesome_owl/static/src/components/todo/todo_list.js +++ b/awesome_owl/static/src/components/todo/todo_list.js @@ -1,12 +1,49 @@ -import { Component, useState } from "@odoo/owl"; +import { Component, useState, useRef } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; +import { useAutofocus } from "../../utils"; -export class Todo extends Component { +export class TodoList extends Component { static template = "awesome_owl.todo_list"; + static components = { TodoItem }; static props = {}; setup() { - this.todos = useState([ - { id: 1, description: "Buy milk", isCompleted: false }, - ]); + this.todos = useState([]); + this.todoCounterId = 0; + + this.inputRef = useRef("inputRef"); + + useAutofocus(this.inputRef); + } + + addTodo(event) { + if (event.keyCode == 13) { + const newTask = event.target.value.trim(); + + if (newTask) { + this.todos.push({ + id: this.todoCounterId, + description: newTask, + isCompleted: false, + }); + this.todoCounterId++; + event.target.value = ""; + } + } + this.todos.push(); + } + + removeTodo = (removeTodoId) => { + const index = this.todos.findIndex((todo) => todo.id === removeTodoId); + if (index !== -1) { + this.todos.splice(index, 1); + } + }; + + toggleTodoState(todoId) { + const todo = this.todos.find((t) => t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } } } diff --git a/awesome_owl/static/src/components/todo/todo_list.xml b/awesome_owl/static/src/components/todo/todo_list.xml new file mode 100644 index 00000000000..aa190a7088c --- /dev/null +++ b/awesome_owl/static/src/components/todo/todo_list.xml @@ -0,0 +1,17 @@ + + + +
+

Todo List

+ + + + +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 67c67a5a348..8a782a6c624 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -3,16 +3,17 @@ import { Component, markup, useState } from "@odoo/owl"; import { Counter } from "./components/counter/counter"; import { Card } from "./components/card/card"; +import { TodoList } from "./components/todo/todo_list"; export class Playground extends Component { static template = "awesome_owl.playground"; - static components = { Counter, Card }; + static components = { Counter, Card, TodoList }; static props = {}; setup() { this.state = useState({ sum: 0 }); } - + card1ContentValue = markup("
Some Content
"); incrementSum() { diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index b89639b4e66..f25370ccf37 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,14 +1,36 @@ -
- - -

Total Sum: - -

- - +
+ +

Counter

+
+ + +
+ + +
+

Total Sum: + +

+
+ + +

Cards

+
+ + + + + + +
+ + +
+ +
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..7c0fceee1ee --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,15 @@ +import { onMounted } from "@odoo/owl"; + +/** + * Automatically focuses on the provided reference element when the component is mounted. + * + * @param {Object} ref - The reference object to the DOM element. + * @returns {void} + */ +export const useAutofocus = (ref) => { + onMounted(() => { + if (ref?.el) { + ref.el.focus(); + } + }); +}; From 58427a74acd7750b5a0fa62228884c4fae7f1511 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Thu, 20 Feb 2025 18:56:43 +0530 Subject: [PATCH 12/13] [IMP] awesome_dashboard: Set up Dashboard with Navigation and Statistics Designed a dashboard layout with quick navigation Utilized a service to fetch and display customers and leads --- awesome_dashboard/static/src/dashboard.js | 35 +++++++++++++++++-- awesome_dashboard/static/src/dashboard.scss | 3 ++ awesome_dashboard/static/src/dashboard.xml | 25 +++++++++++-- .../src/dashboard_item/dashboard_item.js | 18 ++++++++++ .../src/dashboard_item/dashboard_item.xml | 10 ++++++ 5 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 awesome_dashboard/static/src/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard_item/dashboard_item.xml diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js index 637fa4bb972..cc959ddac31 100644 --- a/awesome_dashboard/static/src/dashboard.js +++ b/awesome_dashboard/static/src/dashboard.js @@ -1,10 +1,41 @@ /** @odoo-module **/ +import { _t } from "@web/core/l10n/translation"; import { Component } 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"; class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem }; + + setup() { + this.display = { + controlPanel: {}, + }; + this.action = useService("action"); + } + + showCustomers() { + this.action.doAction("base.action_partner_form"); + } + + showLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: _t("Leads"), + target: "current", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } } -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); +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..6be5e0f83c9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: gray; +} diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml index 1a2ac9a2fed..4dffb4836b4 100644 --- a/awesome_dashboard/static/src/dashboard.xml +++ b/awesome_dashboard/static/src/dashboard.xml @@ -1,8 +1,29 @@ - + - hello dashboard + + + + + + some content +
+ + some content + + + I love milk + + + some content + +
+
diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..4644048af21 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.js @@ -0,0 +1,18 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + slots: { + type: Object, + shape: { + default: Object, + }, + }, + size: { + type: Number, + default: 1, + optional: true, + }, + }; +} diff --git a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..6b3d2923fb2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml @@ -0,0 +1,10 @@ + + + +
+
+ +
+
+
+
From ab7a0ffab23cb064df3e039c5085c88440006013 Mon Sep 17 00:00:00 2001 From: "Shiv Bhadaniya (sbha)" Date: Sun, 23 Feb 2025 12:40:14 +0530 Subject: [PATCH 13/13] [IMP] awesome_dashboard: Improve Dashboard Features Implemented network caching, server calls, and a pie chart. Implemented real-time updates for a dynamic user experience. Added lazy loading to enhance dashboard performance. Made the dashboard generic and extensible for customization. Enabled adding/removing dashboard items dynamically. --- awesome_dashboard/__manifest__.py | 38 ++++--- awesome_dashboard/static/src/dashboard.js | 41 ------- awesome_dashboard/static/src/dashboard.xml | 29 ----- .../static/src/dashboard/dashboard.js | 101 ++++++++++++++++++ .../static/src/{ => dashboard}/dashboard.scss | 0 .../static/src/dashboard/dashboard.xml | 51 +++++++++ .../dashboard_item/dashboard_item.js | 0 .../dashboard_item/dashboard_item.xml | 2 +- .../static/src/dashboard/dashboard_items.js | 65 +++++++++++ .../src/dashboard/number_card/number_card.js | 13 +++ .../src/dashboard/number_card/number_card.xml | 9 ++ .../src/dashboard/pie_chart/pie_chart.js | 37 +++++++ .../src/dashboard/pie_chart/pie_chart.xml | 10 ++ .../pie_chart_card/pie_chart_card.js | 15 +++ .../pie_chart_card/pie_chart_card.xml | 7 ++ .../src/dashboard/statistics_service.js | 36 +++++++ .../static/src/dashboard_loader.js | 13 +++ 17 files changed, 376 insertions(+), 91 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 (83%) 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 create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml 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_service.js create mode 100644 awesome_dashboard/static/src/dashboard_loader.js diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 31406e8addb..c50b9e0c067 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,30 +1,28 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Dashboard", - - 'summary': """ + "name": "Awesome Dashboard", + "summary": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'description': """ + "description": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com/", - 'category': 'Tutorials/AwesomeDashboard', - 'version': '0.1', - 'application': True, - 'installable': True, - 'depends': ['base', 'web', 'mail', 'crm'], - - 'data': [ - 'views/views.xml', + "author": "Odoo", + "website": "https://www.odoo.com/", + "category": "Tutorials/AwesomeDashboard", + "version": "0.1", + "application": True, + "installable": True, + "depends": ["base", "web", "mail", "crm"], + "data": [ + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + "assets": { + "web.assets_backend": [ + "awesome_dashboard/static/src/**/*", + ("remove", "awesome_dashboard/static/src/dashboard/**/*"), ], + "awesome_dashboard.dashboard": ["awesome_dashboard/static/src/dashboard/**/*"], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index cc959ddac31..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,41 +0,0 @@ -/** @odoo-module **/ - -import { _t } from "@web/core/l10n/translation"; -import { Component } 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"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout, DashboardItem }; - - setup() { - this.display = { - controlPanel: {}, - }; - this.action = useService("action"); - } - - showCustomers() { - this.action.doAction("base.action_partner_form"); - } - - showLeads() { - this.action.doAction({ - type: "ir.actions.act_window", - name: _t("Leads"), - target: "current", - 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 4dffb4836b4..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - some content -
- - some content - - - I love milk - - - some content - -
-
-
- -
diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..54dec402fda --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,101 @@ +/** @odoo-module **/ + +import { _t } from "@web/core/l10n/translation"; +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 { 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 template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem }; + + setup() { + this.display = { + controlPanel: {}, + }; + this.action = useService("action"); + + this.orders_details = useState(useService("awesome_dashboard.statistics")); + + this.items = registry.category("awesome_dashboard").getAll(); + + this.dialog = useService("dialog"); + + this.state = useState({ + disabledItems: + browser.localStorage.getItem("disabledDashboardItems")?.split(",") || + [], + }); + } + + openConfiguration() { + this.dialog.add(ConfigurationDialog, { + items: this.items, + disabledItems: this.state.disabledItems, + onUpdateConfiguration: this.updateConfiguration.bind(this), + }); + } + + updateConfiguration(newDisabledItems) { + this.state.disabledItems = newDisabledItems; + } + + showCustomers() { + this.action.doAction("base.action_partner_form"); + } + + showLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: _t("Leads"), + target: "current", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } +} + +class ConfigurationDialog extends Component { + + static template = "awesome_dashboard.ConfigurationDialog"; + static components = { Dialog, CheckBox }; + static props = ["close", "items", "disabledItems", "onUpdateConfiguration"]; + + setup() { + this.items = useState(this.props.items.map((item) => { + return { + ...item, + enabled: !this.props.disabledItems.includes(item.id), + } + })); + } + + done() { + this.props.close(); + } + + 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); + } + +} + +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..c67bbfe6b83 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+
+
+
+
+ + + + Which cards do you whish 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 83% rename from awesome_dashboard/static/src/dashboard_item/dashboard_item.xml rename to awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml index 6b3d2923fb2..56b1b832a0f 100644 --- a/awesome_dashboard/static/src/dashboard_item/dashboard_item.xml +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -1,6 +1,6 @@ - +
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..300e8bbd53e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,65 @@ +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 for an order", + Component: NumberCard, + props: (data) => ({ + title: "Average time for an order to go from 'new' to 'sent' or 'cancelled'", + value: data.average_time, + }) + }, + { + id: "number_new_orders", + description: "New orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Number of new orders this month", + value: data.nb_new_orders, + }) + }, + { + id: "cancelled_orders", + description: "Cancelled orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Number of cancelled orders this month", + value: data.nb_cancelled_orders, + }) + }, + { + id: "amount_new_orders", + description: "amount orders this month", + Component: NumberCard, + props: (data) => ({ + title: "Total amount of new orders this month", + value: data.total_amount, + }) + }, + { + id: "pie_chart", + description: "Shirt orders by size", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "Shirt orders by size", + values: 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..d3bd9c0e4ef --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,13 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: { + type: String, + }, + value: { + type: Number, + } + } +} \ No newline at end of file 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..3a0713623fa --- /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..63b0870e620 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,37 @@ +import { Component, onWillStart, useState, useRef, onMounted, onWillUnmount } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + data: Object, + }; + + setup() { + this.canvasRef = useRef("canvas"); + onWillStart(() => loadJS("/web/static/lib/Chart/Chart.js")); + onMounted(() => { + this.renderChart(); + }); + onWillUnmount(() => { + this.chart.destroy(); + }); + } + + renderChart() { + 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/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..4f3c54a6c15 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -0,0 +1,10 @@ + + + +
+
+ +
+
+
+
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..3faac175fed --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,15 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../pie_chart/pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { PieChart } + static props = { + title: { + type: String, + }, + values: { + type: Object, + }, + } +} 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..58a6811c83a --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js new file mode 100644 index 00000000000..95c9b53d3e2 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,36 @@ +/** @odoo-module **/ +import { rpc } from "@web/core/network/rpc"; +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; + + +export const statisticsService = { + + start() { + const statistics = reactive({ isLoading: false }); + + const loadStatistics = async () => { + statistics.isLoading = true; + try { + const updated_value = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, updated_value, {isLoading: false}); + } catch (error) { + console.error("Failed to load statistics:", error); + statistics.isLoading = false; + } + }; + + // Initial Load + loadStatistics(); + + // Auto-refresh every 10 seconds + setInterval(loadStatistics, 100000); + + + return statistics; + }, +}; + +registry + .category("services") + .add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_loader.js b/awesome_dashboard/static/src/dashboard_loader.js new file mode 100644 index 00000000000..a5bdc15e1e9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,13 @@ +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; +import { Component, xml } from "@odoo/owl"; + +class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; + +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader);