From d256e4cc3347457453201a84b2090bcab4c25fe5 Mon Sep 17 00:00:00 2001 From: ksoz Date: Thu, 3 Jul 2025 10:54:05 +0530 Subject: [PATCH 1/7] [ADD] estate_property: initial creation of app and estate_property model Creation of Estate App and properly understanding the flow of creation of app. Creating models folder and adding estate property model with mentioned field --- estate/__init__.py | 1 + estate/__manifest__.py | 13 +++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 27 +++++++++++++++++++++++++++ 4 files changed, 42 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 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..013094e5407 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,13 @@ +{ + 'name': 'Estate', + 'version': '1.0', + 'summary': 'Real estate management module', + 'license': 'LGPL-3', + 'description': 'Manage properties, owners, and sales in your real estate agency', + 'category': 'Real Estate', + 'author': 'ksoz', + 'depends': ['base'], + 'data': [], + 'installable': True, + 'application': True, +} 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..e97ffdfc866 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import fields,models + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="PostCode") + date_availability = fields.Date(string="Available From") + expected_price = fields.Float(required=True, string="Expected Price") + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bedroom") + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area") + garden_orientation = fields.Selection( + [ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ], + string="Garden Orientation" + ) From 3589778b75755ab10c5ea0e3d2904074dde354cd Mon Sep 17 00:00:00 2001 From: ksoz Date: Fri, 4 Jul 2025 10:33:20 +0530 Subject: [PATCH 2/7] [ADD] estate_property: security files and search, list, filter added Added security files for estate_property module Implemented search functionality for estate properties Developed listing and filtering features for better property management --- .gitignore | 3 + estate/__manifest__.py | 7 +- estate/models/estate_property.py | 29 ++++++--- estate/security/ir.model.access.csv | 2 + estate/views/estate_menus.xml | 6 ++ estate/views/estate_property_views.xml | 89 ++++++++++++++++++++++++++ 6 files changed, 126 insertions(+), 10 deletions(-) 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/.gitignore b/.gitignore index b6e47617de1..8fdcc43f6c9 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + + +.vscode/ \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 013094e5407..baad6861239 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,13 +1,18 @@ { 'name': 'Estate', 'version': '1.0', + 'license': 'LGPL-3', 'summary': 'Real estate management module', 'license': 'LGPL-3', 'description': 'Manage properties, owners, and sales in your real estate agency', 'category': 'Real Estate', 'author': 'ksoz', 'depends': ['base'], - 'data': [], + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + ], 'installable': True, 'application': True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e97ffdfc866..31bd2ab6abe 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,13 +1,18 @@ -from odoo import fields,models +from odoo import fields, models + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" name = fields.Char(required=True) - description = fields.Text(string="Description") + description = fields.Text(string="Title") postcode = fields.Char(string="PostCode") - date_availability = fields.Date(string="Available From") + date_availability = fields.Date( + string="Available From", + copy=False, + default=lambda self: fields.Date.add(fields.Date.today(), months=3), + ) expected_price = fields.Float(required=True, string="Expected Price") selling_price = fields.Float(string="Selling Price") bedrooms = fields.Integer(string="Bedroom") @@ -17,11 +22,17 @@ class EstateProperty(models.Model): garden = fields.Boolean(string="Garden") garden_area = fields.Integer(string="Garden Area") garden_orientation = fields.Selection( + [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], + string="Garden Orientation", + ) + active = fields.Boolean(default=True) + state = fields.Selection( [ - ('north', 'North'), - ('south', 'South'), - ('east', 'East'), - ('west', 'West') - ], - string="Garden Orientation" + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ],string="Status", copy=False, required=True, default="new" ) + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..d9d6ba57cc5 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..c065c4e3787 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..9b2c6b3ac4f --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,89 @@ + + + + Properties + estate.property + list,form + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.view.search + estate.property + + + + + + + + + + + + + + + + + +
\ No newline at end of file From b5bd83ffcdd15537be30edbe9c9b0f5da8830297 Mon Sep 17 00:00:00 2001 From: ksoz Date: Mon, 7 Jul 2025 10:07:45 +0530 Subject: [PATCH 3/7] [ADD] estate_property: offer model add, computed fields and relations Added estate property model and tags model. Made computed fields and inverse fields. Also Learnt about button action. --- estate/__manifest__.py | 4 +- estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 54 +++++++++++++++++-- estate/models/estate_property_offer.py | 55 ++++++++++++++++++++ estate/models/estate_property_tags.py | 7 +++ estate/models/estate_property_type.py | 7 +++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 28 ++++++++-- estate/views/estate_property_offer_views.xml | 38 ++++++++++++++ estate/views/estate_property_tags_views.xml | 34 ++++++++++++ estate/views/estate_property_types_views.xml | 34 ++++++++++++ estate/views/estate_property_views.xml | 22 +++++++- 12 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tags.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tags_views.xml create mode 100644 estate/views/estate_property_types_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index baad6861239..86ac4ba0463 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,14 +3,16 @@ 'version': '1.0', 'license': 'LGPL-3', 'summary': 'Real estate management module', - 'license': 'LGPL-3', 'description': 'Manage properties, owners, and sales in your real estate agency', 'category': 'Real Estate', 'author': 'ksoz', 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_types_views.xml', 'views/estate_property_views.xml', + 'views/estate_property_tags_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_menus.xml', ], 'installable': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..f6ceb1825e3 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_tags +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 31bd2ab6abe..23fda5fa3a6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import fields, models - +from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): _name = "estate.property" @@ -33,6 +33,54 @@ class EstateProperty(models.Model): ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled", "Cancelled"), - ],string="Status", copy=False, required=True, default="new" + ], + string="Status", + copy=False, + required=True, + default="new", ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + seller_id = fields.Many2one( + "res.users", string="Salesman", default=lambda self: self.env.user + ) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + tag_ids = fields.Many2many("estate.property.tag", string="Tags") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + total_area = fields.Integer(compute="_compute_total_area") + best_offer = fields.Float(compute="_compute_best_offer") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_offer(self): + for record in self: + if record.offer_ids: + record.best_offer = max(record.offer_ids.mapped("price")) + else: + record.best_offer = 0.0 + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = False + + def action_on_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("A cancelled property cannot be sold") + record.state = "sold" + return True + def action_on_cancelled(self): + for record in self: + if record.state == "sold": + raise UserError("A sold property cannot be cancelled.") + record.state = "cancelled" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..9f51e77e369 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,55 @@ +from odoo import api, fields, models +from odoo.exceptions import UserError + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Offers for estate property" + price = fields.Float() + status = fields.Selection( + [("accepted", "Accepted"), ("refused", "Refused")], 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 (days)", default=7) + date_deadline = fields.Date( + string="Deadline", + compute="_compute_date_deadline", + inverse="_inverse_date_deadline" + ) + status = fields.Selection( + selection=[('accepted', 'Accepted'), ('refused', 'Refused')], + string="Status", + copy=False + ) + + @api.depends("validity") + def _compute_date_deadline(self): + for record in self: + start_date = record.create_date or fields.Date.context_today(record) + record.date_deadline = fields.Date.add(start_date, days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + start_date = record.create_date or fields.Date.context_today(record) + if record.date_deadline: + delta = record.date_deadline - start_date.date() + record.validity = delta.days + else: + record.validity = 0 + + def action_on_accepted(self): + for offer in self: + accepted_offers = offer.property_id.offer_ids.filtered(lambda o: o.status == 'accepted') + if accepted_offers: + raise UserError("An offer has already been accepted for this property.") + + offer.status = 'accepted' + offer.property_id.selling_price = offer.price + offer.property_id.buyer_id = offer.partner_id + offer.property_id.state = 'offer_accepted' + return True + + def action_on_refused(self): + for offer in self: + offer.status = "refused" + return True \ No newline at end of file diff --git a/estate/models/estate_property_tags.py b/estate/models/estate_property_tags.py new file mode 100644 index 00000000000..99545cb076e --- /dev/null +++ b/estate/models/estate_property_tags.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name="estate.property.tag" + _description="Tags For Estate Properties" + + name = fields.Char(required=True, string="Tag") \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..b7452f85429 --- /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="Types of Real Estate Properties" + + name = fields.Char(required=True, string="Property Type") \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d9d6ba57cc5..7a022f67747 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,estate.property.offer.user,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index c065c4e3787..514cf9107b6 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,6 +1,26 @@ - - - - \ No newline at end of file + + + + + + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..b5af0ca8746 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,38 @@ + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + +

- +

+ + + + + + + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 33264a589dd..205370c7342 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,20 +4,25 @@ Properties estate.property list,form + {'search_default_available': True}
estate.property.list estate.property - + + - + @@ -28,8 +33,10 @@
-
@@ -41,8 +48,10 @@ - - + + @@ -60,8 +69,8 @@ - - + + @@ -77,7 +86,8 @@ - + @@ -95,7 +105,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..1e68d6ac9c5 --- /dev/null +++ b/estate/views/estate_res_users_views.xml @@ -0,0 +1,15 @@ + + + + res.users.form.inherited.estate + res.users + + + + + + + + + + \ No newline at end of file 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..b63acc4b968 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,19 @@ +{ + "name": "Estate Account", + "version": "1.0", + "author": "ksoz", + "license": "LGPL-3", + "category": "Real Estate/Brokerage", + "description": """ + This module links the Real Estate module with the Accounting module. + """, + "depends": [ + "estate", + "account", + ], + "data": [ + ], + "installable": True, + "application": False, + "auto_install": False, +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..642cac09f55 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,33 @@ +from odoo import models, Command + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_on_sold(self): + res = super().action_on_sold() + + for record in self: + invoice_vals = { + 'partner_id': record.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create( + { + "name": self.name, + "quantity": 1, + "price_unit": 0.06 * self.selling_price, + } + ), + Command.create( + { + "name": "administrative fees", + "quantity": 1, + "price_unit": 100.00, + } + ), + ], + } + self.env['account.move'].create(invoice_vals) + + return res From 925c9474104510f5bd44939fadacb0fa4cfad1c5 Mon Sep 17 00:00:00 2001 From: ksoz Date: Wed, 16 Jul 2025 10:28:45 +0530 Subject: [PATCH 5/7] [ADD] estate_property: security and groups for agent and manager Added security for agent and manager specifying about what they can view and what they can edit Added report and everything properly Update the security csv removed base group user and Added separate role for agent and manager --- estate/__manifest__.py | 7 + estate/data/estate.property.type.csv | 5 + estate/demo/property_demo_data.xml | 56 +++++++ estate/models/estate_property.py | 1 + estate/report/estate_property_report.xml | 21 +++ estate/report/estate_property_templates.xml | 161 ++++++++++++++++++++ estate/security/ir.model.access.csv | 15 +- estate/security/security.xml | 48 ++++++ estate/views/estate_property_views.xml | 2 +- estate/views/estate_res_users_views.xml | 2 +- 10 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 estate/data/estate.property.type.csv create mode 100644 estate/demo/property_demo_data.xml create mode 100644 estate/report/estate_property_report.xml create mode 100644 estate/report/estate_property_templates.xml create mode 100644 estate/security/security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f28b6fc70de..1c6bee8fdd9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,13 +8,20 @@ 'author': 'ksoz', 'depends': ['base'], 'data': [ + 'security/security.xml', 'security/ir.model.access.csv', + 'data/estate.property.type.csv', 'views/estate_property_offer_views.xml', 'views/estate_property_types_views.xml', 'views/estate_property_views.xml', 'views/estate_property_tags_views.xml', "views/estate_res_users_views.xml", 'views/estate_menus.xml', + 'report/estate_property_templates.xml', + 'report/estate_property_report.xml', + ], + 'demo': [ + 'demo/property_demo_data.xml', ], 'installable': True, 'application': True, diff --git a/estate/data/estate.property.type.csv b/estate/data/estate.property.type.csv new file mode 100644 index 00000000000..4d0d7a60f78 --- /dev/null +++ b/estate/data/estate.property.type.csv @@ -0,0 +1,5 @@ +"id","name" +property_type_residential,"Residential" +property_type_commercial,"Commercial" +property_type_industrial,"Industrial" +property_type_land,"Land" diff --git a/estate/demo/property_demo_data.xml b/estate/demo/property_demo_data.xml new file mode 100644 index 00000000000..6d4ab378213 --- /dev/null +++ b/estate/demo/property_demo_data.xml @@ -0,0 +1,56 @@ + + + Residential + + + Big Villa + A nice and big villa + 12345 + + 1600000.0 + 6 + 100 + 4 + + + 100000 + south + + + + Trailer home + cancelled + Home in a trailer park + 54321 + + 100000.0 + 99000.0 + 1 + 10 + 4 + + + + + + Apartment with Inline Offers + 250000.0 + + + + + + + 1500000 + + + + + 1500001 + + + + + 1500002 + + \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4b1e58dbc3b..9468a5dbdda 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -51,6 +51,7 @@ class EstateProperty(models.Model): offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") total_area = fields.Integer(compute="_compute_total_area") best_offer = fields.Float(compute="_compute_best_offer") + company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company) _sql_constraints = [ ( diff --git a/estate/report/estate_property_report.xml b/estate/report/estate_property_report.xml new file mode 100644 index 00000000000..d5dc507bed7 --- /dev/null +++ b/estate/report/estate_property_report.xml @@ -0,0 +1,21 @@ + + + + + Property Offers + estate.property + qweb-pdf + estate.report_property_offers_document estate.report_property_offers_document 'Property Offers Report for %s' % object.name + report + + + + + Salesman Properties res.users + qweb-pdf + estate.report_salesman_properties + estate.report_salesman_properties + 'Salesman Property Offers for %s' % object.name + report + + diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml new file mode 100644 index 00000000000..40bf6e00d71 --- /dev/null +++ b/estate/report/estate_property_templates.xml @@ -0,0 +1,161 @@ + + + + + + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 7a022f67747..9fc6d823dc3 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,10 @@ -id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 -access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer,estate.property.offer.user,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property_manager,access_estate_property_manager,model_estate_property,estate_group_manager,1,1,1,0 +access_estate_property_type_manager,access_estate_property_type_manager,model_estate_property_type,estate_group_manager,1,1,1,1 +access_estate_property_tag_manager,access_estate_property_tag_manager,model_estate_property_tag,estate_group_manager,1,1,1,1 +access_estate_offer_manager,access_estate_offer_manager,model_estate_property_offer,estate_group_manager,1,1,1,1 + +access_estate_property_user,access_estate_property_user,model_estate_property,estate_group_user,1,1,1,0 +access_estate_property_type_user,access_estate_property_type_user,model_estate_property_type,estate_group_user,1,1,0,0 +access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,estate_group_user,1,0,0,0 +access_estate_property_offer_user,access_estate_property_offer_user,model_estate_property_offer,estate_group_user,1,1,1,1 diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 00000000000..55abc0f98cb --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,48 @@ + + + Agent + + + + + Manager + + + + + + + Agents: Own or Unassigned Properties Only + + + [ + '|', + ('seller_id', '=', user.id), + ('seller_id', '=', False) + ] + + + + + + + Managers: Full Access to Properties + + + [(1, '=', 1)] + + + + + + + Estate Property Multi-company + + [ + ('company_id', 'in', company_ids) + ] + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 205370c7342..c3036cddd5b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -114,4 +114,4 @@ - \ No newline at end of file + diff --git a/estate/views/estate_res_users_views.xml b/estate/views/estate_res_users_views.xml index 1e68d6ac9c5..a6d88ee5629 100644 --- a/estate/views/estate_res_users_views.xml +++ b/estate/views/estate_res_users_views.xml @@ -12,4 +12,4 @@ - \ No newline at end of file + From 5f9229facef39e8cb9418e905204822879d88860 Mon Sep 17 00:00:00 2001 From: ksoz Date: Thu, 17 Jul 2025 10:57:06 +0530 Subject: [PATCH 6/7] [ADD] awesome_owl: started owl framework, Todo List Application Implemented fundamental Owl concepts including: Initial project setup and basic rendering. State management with and refs with . Reusable Counter component with sum functionality. Solid understanding and application of props. Adhered to proper folder structure. Developed a complete Todo List application(add, remove, mark as completed). --- awesome_owl/static/src/Todo/todo_item.js | 28 +++++++++++++++ awesome_owl/static/src/Todo/todo_item.xml | 19 ++++++++++ awesome_owl/static/src/Todo/todo_list.js | 41 ++++++++++++++++++++++ awesome_owl/static/src/Todo/todo_list.xml | 18 ++++++++++ awesome_owl/static/src/card/card.js | 16 +++++++++ awesome_owl/static/src/card/card.xml | 16 +++++++++ awesome_owl/static/src/counter/counter.js | 21 +++++++++++ awesome_owl/static/src/counter/counter.xml | 10 ++++++ awesome_owl/static/src/playground.js | 28 ++++++++++++--- awesome_owl/static/src/playground.xml | 31 +++++++++++++--- 10 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 awesome_owl/static/src/Todo/todo_item.js create mode 100644 awesome_owl/static/src/Todo/todo_item.xml create mode 100644 awesome_owl/static/src/Todo/todo_list.js create mode 100644 awesome_owl/static/src/Todo/todo_list.xml create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml diff --git a/awesome_owl/static/src/Todo/todo_item.js b/awesome_owl/static/src/Todo/todo_item.js new file mode 100644 index 00000000000..b57450e685b --- /dev/null +++ b/awesome_owl/static/src/Todo/todo_item.js @@ -0,0 +1,28 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todo_item"; + static props = { + todo: { + type: Object, + shape: { + id: { type: Number, default: 0 }, + description: { type: String }, + isCompleted: { type: Boolean }, + }, + }, + removeTodo: { type: Function, optional: true }, + toggleState: { type: Function, optional: true }, + }; + + onToggle() { + if (this.props.toggleState) { + this.props.toggleState(this.props.todo.id); + } + } + onRemoveTodo() { + if (this.props.removeTodo) { + this.props.removeTodo(this.props.todo.id); + } + } +} diff --git a/awesome_owl/static/src/Todo/todo_item.xml b/awesome_owl/static/src/Todo/todo_item.xml new file mode 100644 index 00000000000..b1214b17f35 --- /dev/null +++ b/awesome_owl/static/src/Todo/todo_item.xml @@ -0,0 +1,19 @@ + + + +
  • +
    + + # + + + + + +
    + + + +
  • +
    +
    diff --git a/awesome_owl/static/src/Todo/todo_list.js b/awesome_owl/static/src/Todo/todo_list.js new file mode 100644 index 00000000000..ca9f70aa7fb --- /dev/null +++ b/awesome_owl/static/src/Todo/todo_list.js @@ -0,0 +1,41 @@ +import { Component, useState } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; + +export class TodoList extends Component { + static template = "awesome_owl.todo_list"; + static components = { TodoItem }; + static props = {}; + + setup() { + this.todos = useState([]); + } + + toggleState = (id) => { + const todo = this.todos.find((t) => t.id === id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + }; + + addTodo(ev) { + if (ev.keyCode !== 13) return; + const description = ev.target.value.trim(); + + if (!description) return; + + this.todos.push({ + id: this.todos.length + 1, + description: description, + isCompleted: false, + }); + + ev.target.value = ""; + } + + onRemoveTodo = (todoId) => { + const index = this.todos.findIndex((elem) => elem.id === todoId); + if (index >= 0) { + this.todos.splice(index, 1); + } + }; +} diff --git a/awesome_owl/static/src/Todo/todo_list.xml b/awesome_owl/static/src/Todo/todo_list.xml new file mode 100644 index 00000000000..5c43f78416d --- /dev/null +++ b/awesome_owl/static/src/Todo/todo_list.xml @@ -0,0 +1,18 @@ + + + +
    +
    +
    +

    📝 My Todo List

    + +
      + + + +
    +
    +
    +
    +
    +
    diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..eacc3d83ee0 --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,16 @@ +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 }, + slots: {}, + }; + + setup() { + this.state = { + isOpen: true, + }; + } +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..ad764e3573b --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,16 @@ + + + +
    +
    +
    + +
    +

    + +

    + +
    +
    +
    +
    \ No newline at end of file diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..e53ed0e69ad --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,21 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = { + onChange: { type: Function, optional: true }, + }; + + setup() { + this.state = useState({ + value: 0, + }); + } + + increment() { + this.state.value += 1; + if (this.props.onChange) { + this.props.onChange(this.state.value); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..7292abd0caa --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,10 @@ + + + +
    +

    Counter: +

    + +
    +
    +
    \ No newline at end of file diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..38f3c9dda56 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,27 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./Todo/todo_list"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + static props = []; + + setup() { + this.state = useState({ + sum: 0, + }); + } + + incrementSum = () => { + this.state.sum++; + }; + + toggle = () => { + this.state.isOpen = !this.state.isOpen; + }; + + htmlString = "
    This is red text
    "; + safeHtmlString = markup("
    This is green text
    "); } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..da060b84e5d 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,33 @@ - -
    +
    hello world +
    + + +
    - +
    + +

    This is plain text inside a card.

    +
    + + + + + + + +
    +

    Sum: +

    - +
    +

    Todo App

    + +
    + + From 59f6a64e64a3153880b76f2576360e70ff0ea6ca Mon Sep 17 00:00:00 2001 From: ksoz Date: Fri, 18 Jul 2025 10:05:04 +0530 Subject: [PATCH 7/7] [ADD] awesome_dashboard: implement interactive lazy-loaded dashboard add dashboard layout, navigation, stats, and lazy loading Integrated Layout component with control panel Added Customers and Leads navigation buttons Created reusable DashboardItem component with size prop Fetched and displayed stats via RPC (/statistics route) Registered statistics service with caching (memoize) Added PieChart with lazy-loaded Chart.js Made stats reactive with periodic refresh (10s) Moved dashboard to /dashboard and enabled lazy loading via LazyComponent --- awesome_dashboard/static/src/dashboard.js | 10 ---- awesome_dashboard/static/src/dashboard.xml | 8 --- .../static/src/dashboard/dashboard.js | 37 ++++++++++++++ .../static/src/dashboard/dashboard.scss | 3 ++ .../static/src/dashboard/dashboard.xml | 49 +++++++++++++++++++ .../static/src/dashboard/dashboard_item.js | 0 .../dashboard_item/dashboard_item.js | 20 ++++++++ .../dashboard_item/dashboard_item.scss | 9 ++++ .../dashboard_item/dashboard_item.xml | 10 ++++ .../dashboard/number_card.js/number_card.js | 15 ++++++ .../dashboard/number_card.js/number_card.xml | 9 ++++ .../static/src/dashboard/piechart/PieChart.js | 41 ++++++++++++++++ .../src/dashboard/piechart/PieChart.xml | 10 ++++ .../static/src/dashboard_loader.js | 15 ++++++ .../static/src/statistics_service.js | 24 +++++++++ 15 files changed, 242 insertions(+), 18 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 create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.scss create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/dashboard/number_card.js/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card.js/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/piechart/PieChart.js create mode 100644 awesome_dashboard/static/src/dashboard/piechart/PieChart.xml create mode 100644 awesome_dashboard/static/src/dashboard_loader.js create mode 100644 awesome_dashboard/static/src/statistics_service.js diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 637fa4bb972..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -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 1a2ac9a2fed..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..e173f862b67 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,37 @@ +import { Component, onWillStart, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { PieChart } from "./piechart/PieChart"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, PieChart }; + + setup() { + this.action = useService("action"); + this.display = { + controlPanel: {}, + }; + this.statistics = useState(useService("awesome_dashboard.statistics")); + } + + openCustomer() { + this.action.doAction("base.action_partner_form"); + } + + openLead() { + this.action.doAction({ + type: "ir.actions.act_window", + name: "Leads", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } +} + +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..e2234a6f3f1 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: rgb(46, 255, 108); +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..f96a7344eaa --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,49 @@ + + + + + + + + + +
    + + Average Quantity of new orders this month +
    + +
    +
    + + Average time for an order to go from 'new' to 'sent' or 'cancelled' +
    + +
    +
    + + Number of new orders this month +
    + +
    +
    + + Number of cancelled orders this month +
    + +
    +
    + + Total amount of new orders this month +
    + +
    +
    + + Shirt orders by size + + +
    +
    +
    + +
    diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..32ffb966443 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,20 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { + type: Number, + optional: true, + default: 1, + }, + slots: { + type: Object, + optional: true, + } + } + + get cardStyle() { + return `width: ${this.props.size * 18}rem;`; + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.scss b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.scss new file mode 100644 index 00000000000..d232a817a2d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.scss @@ -0,0 +1,9 @@ +.o_dashboard_card { + background-color: white; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + padding: 16px; + margin: 10px; + display: inline-block; + vertical-align: top; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..b025433ac0d --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,10 @@ + + + + +
    + +
    +
    + +
    diff --git a/awesome_dashboard/static/src/dashboard/number_card.js/number_card.js b/awesome_dashboard/static/src/dashboard/number_card.js/number_card.js new file mode 100644 index 00000000000..a504a0e3d06 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card.js/number_card.js @@ -0,0 +1,15 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: { + type: String, + optional: false, + }, + value: { + type: Number, + optional: false, + }, + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/number_card.js/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card.js/number_card.xml new file mode 100644 index 00000000000..73bc3cac926 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card.js/number_card.xml @@ -0,0 +1,9 @@ + + + + +
    + +
    +
    +
    \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/piechart/PieChart.js b/awesome_dashboard/static/src/dashboard/piechart/PieChart.js new file mode 100644 index 00000000000..e56533235ac --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/piechart/PieChart.js @@ -0,0 +1,41 @@ +import { loadJS } from "@web/core/assets"; +import { getColor } from "@web/core/colors/colors"; +import { Component, onWillStart, useRef, onMounted, onWillUnmount } from "@odoo/owl"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + label: String, + 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); + const color = labels.map((_, index) => getColor(index)); + this.chart = new Chart(this.canvasRef.el, { + type: "pie", + data: { + labels: labels, + datasets: [ + { + label: this.props.label, + data: data, + backgroundColor: color, + }, + ], + }, + }); + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/piechart/PieChart.xml b/awesome_dashboard/static/src/dashboard/piechart/PieChart.xml new file mode 100644 index 00000000000..14e6684262c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/piechart/PieChart.xml @@ -0,0 +1,10 @@ + + + +
    +
    + +
    +
    +
    +
    diff --git a/awesome_dashboard/static/src/dashboard_loader.js b/awesome_dashboard/static/src/dashboard_loader.js new file mode 100644 index 00000000000..06ae51de339 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_loader.js @@ -0,0 +1,15 @@ +/** @odoo-module */ + +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); \ No newline at end of file diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js new file mode 100644 index 00000000000..57040608df2 --- /dev/null +++ b/awesome_dashboard/static/src/statistics_service.js @@ -0,0 +1,24 @@ +import { registry } from "@web/core/registry"; +import { reactive } from "@odoo/owl"; +import { rpc } from "@web/core/network/rpc"; + +const statisticsService = { + + start(env) { + const statistics = reactive({ isReady: false }); + + async function loadData() { + const updates = await rpc("/awesome_dashboard/statistics"); + Object.assign(statistics, updates, { isReady: true }); + } + + setInterval(loadData, 10 * 60 * 1000); + loadData(); + + return statistics; + }, +}; + +registry + .category("services") + .add("awesome_dashboard.statistics", statisticsService);