From 2c039874cc45eaefec0a6525b32b8371a544a1a9 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Mon, 25 Aug 2025 14:17:47 +0530 Subject: [PATCH 01/14] [ADD] estate: Added Real Estate Module Created Module in Tutorial Repo Added Manifest File for Module Added Models Folder Added Views Folder Added Security Added different views like list and form --- estate/__init__.py | 1 + estate/__manifest__.py | 33 ++++++++++++++++++++++++++++++++ estate/models/__init__.py | 4 ++++ estate/models/estate_property.py | 25 ++++++++++++++++++++++++ 4 files changed, 63 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..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..7bb03503512 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'ESTATE', + 'version': '1.0', + 'category': 'Estate', + 'sequence': 15, + 'summary': 'Module to track all things related to real estate of any company', + 'description': "", + 'website': 'https://www.odoo.com/page/estate', + "application": True, + 'depends': [ + 'base_setup', + 'sales_team', + 'mail', + 'calendar', + 'resource', + 'utm', + 'web_tour', + 'contacts', + 'digest', + 'phone_validation', + ], + 'data': [ + ], + 'demo': [ + ], + 'css': ['static/src/css/crm.css'], + 'installable': True, + 'application': True, + 'auto_install': False +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..0c4a347c60d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..c7601fa7828 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +from odoo import models, fields + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + + name = fields.Char(string='Estate Property Name', required=True) + description = fields.Text(string='Estate Property Description') + postcode = fields.Char(string='Estate Property Postcode') + date_availability = fields.Date(string='Estate Property Date Availability') + expected_price = fields.Float(string='Expected Price Of Property', required=True) + selling_price = fields.Float(string='Selling Price of Property') + bedrooms = fields.Integer(string='Number of Bedrooms in Property') + living_area = fields.Integer(string='Number of Living Room in Property') + facades = fields.Integer(string='Number of Facades in Property') + garage = fields.Boolean(string='Property have garage or not') + garden = fields.Boolean(string='Property have Garden or not') + garden_area = fields.Integer(string='Number of Garden Area') + garden_orientation = fields.Selection( + string='Orientation of Garden', + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + help="Different Types of Directions") From fbf7fa364e02a7a950ab9b14ef25c81e8ec65fd3 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Mon, 25 Aug 2025 15:04:08 +0530 Subject: [PATCH 02/14] [IMP] estate: Added access rights in module Updated manifest file Added security folder Added CSV for access right for security --- estate/__manifest__.py | 12 ++---------- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 4 insertions(+), 10 deletions(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7bb03503512..05c3aaf2353 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,18 +11,10 @@ 'website': 'https://www.odoo.com/page/estate', "application": True, 'depends': [ - 'base_setup', - 'sales_team', - 'mail', - 'calendar', - 'resource', - 'utm', - 'web_tour', - 'contacts', - 'digest', - 'phone_validation', + 'base' ], 'data': [ + "security/ir.model.access.csv" ], 'demo': [ ], diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..fd62cec345a --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +"id","name","model_id/id","group_id/id","perm_read","perm_write","perm_create","perm_unlink" +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 From d2edb0b5b8d870da4db4797a9ac491c9efceaa08 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Tue, 26 Aug 2025 10:52:11 +0530 Subject: [PATCH 03/14] [IMP] estate: Added Views for the module Added root menu view Added sub menu view Changed model configuration Added new attributes in fields like copy, readonly, and default Added new field state --- estate/__manifest__.py | 5 ++++- estate/models/estate_property.py | 25 ++++++++++++++++++++----- estate/views/estate_property_menus.xml | 8 ++++++++ estate/views/estate_property_views.xml | 8 ++++++++ 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 estate/views/estate_property_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 05c3aaf2353..67a5c4b4d56 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,10 +14,13 @@ 'base' ], 'data': [ - "security/ir.model.access.csv" + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_property_menus.xml" ], 'demo': [ ], + "license": "LGPL-3", 'css': ['static/src/css/crm.css'], 'installable': True, 'application': True, diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c7601fa7828..7f2c57f48f5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from datetime import datetime, timedelta from odoo import models, fields @@ -10,10 +11,14 @@ class EstateProperty(models.Model): name = fields.Char(string='Estate Property Name', required=True) description = fields.Text(string='Estate Property Description') postcode = fields.Char(string='Estate Property Postcode') - date_availability = fields.Date(string='Estate Property Date Availability') - expected_price = fields.Float(string='Expected Price Of Property', required=True) - selling_price = fields.Float(string='Selling Price of Property') - bedrooms = fields.Integer(string='Number of Bedrooms in Property') + date_availability = fields.Date(string='Estate Property Date Availability', + copy=False, default=lambda self: datetime.now() + timedelta(days=90)) + expected_price = fields.Float( + string='Expected Price Of Property', required=True) + selling_price = fields.Float( + string='Selling Price of Property', readonly=True, copy=False) + bedrooms = fields.Integer( + string='Number of Bedrooms in Property', default=2) living_area = fields.Integer(string='Number of Living Room in Property') facades = fields.Integer(string='Number of Facades in Property') garage = fields.Boolean(string='Property have garage or not') @@ -21,5 +26,15 @@ class EstateProperty(models.Model): garden_area = fields.Integer(string='Number of Garden Area') garden_orientation = fields.Selection( string='Orientation of Garden', - selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + selection=[('north', 'North'), ('south', 'Sou hhjghjth'), + + ('east', 'East'), ('west', 'West')], help="Different Types of Directions") + active = fields.Boolean(default=True) + state = fields.Selection( + string='State', + default="new", + copy=False, + required=True, + selection=[('new' , 'New'), ('offer_recieved' , 'Offer Received'), ('offer_accepted','Offer Accepted'), ('sold_and_cancelled', 'Sold and Cancelled')], + help="State of the property") diff --git a/estate/views/estate_property_menus.xml b/estate/views/estate_property_menus.xml new file mode 100644 index 00000000000..92f95bf24b6 --- /dev/null +++ b/estate/views/estate_property_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + \ 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..daca2b9c4ff --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate.property + list,form + + \ No newline at end of file From e587d1491c73c3d0f8b8341c1d9ab153b0d88511 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Tue, 26 Aug 2025 16:05:23 +0530 Subject: [PATCH 04/14] [IMP] estate: Added Form view Added List View Added Search View Added Filter View Added Group By view Styled the Form View --- estate/__manifest__.py | 4 +- estate/models/estate_property.py | 21 ++++---- estate/views/estate_property_views.xml | 75 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 14 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 67a5c4b4d56..4e491bfc11f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -16,12 +16,12 @@ 'data': [ "security/ir.model.access.csv", "views/estate_property_views.xml", - "views/estate_property_menus.xml" + "views/estate_property_menus.xml", ], 'demo': [ ], "license": "LGPL-3", - 'css': ['static/src/css/crm.css'], + 'css': [''], 'installable': True, 'application': True, 'auto_install': False diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7f2c57f48f5..bf550e5e0b9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -11,14 +11,10 @@ class EstateProperty(models.Model): name = fields.Char(string='Estate Property Name', required=True) description = fields.Text(string='Estate Property Description') postcode = fields.Char(string='Estate Property Postcode') - date_availability = fields.Date(string='Estate Property Date Availability', - copy=False, default=lambda self: datetime.now() + timedelta(days=90)) - expected_price = fields.Float( - string='Expected Price Of Property', required=True) - selling_price = fields.Float( - string='Selling Price of Property', readonly=True, copy=False) - bedrooms = fields.Integer( - string='Number of Bedrooms in Property', default=2) + date_availability = fields.Date(string='Estate Property Date Availability', copy=False, default=lambda self: datetime.now() + timedelta(days=90)) + expected_price = fields.Float(string='Expected Price Of Property', required=True) + selling_price = fields.Float(string='Selling Price of Property', readonly=True, copy=False) + bedrooms = fields.Integer(string='Number of Bedrooms in Property', default=2) living_area = fields.Integer(string='Number of Living Room in Property') facades = fields.Integer(string='Number of Facades in Property') garage = fields.Boolean(string='Property have garage or not') @@ -26,9 +22,10 @@ class EstateProperty(models.Model): garden_area = fields.Integer(string='Number of Garden Area') garden_orientation = fields.Selection( string='Orientation of Garden', - selection=[('north', 'North'), ('south', 'Sou hhjghjth'), - - ('east', 'East'), ('west', 'West')], + selection=[('north', 'North'), + ('south', 'Sou hhjghjth'), + ('east', 'East'), + ('west', 'West')], help="Different Types of Directions") active = fields.Boolean(default=True) state = fields.Selection( @@ -36,5 +33,5 @@ class EstateProperty(models.Model): default="new", copy=False, required=True, - selection=[('new' , 'New'), ('offer_recieved' , 'Offer Received'), ('offer_accepted','Offer Accepted'), ('sold_and_cancelled', 'Sold and Cancelled')], + selection=[('new', 'New'), ('offer_recieved', 'Offer Received'), ('offer_accepted' , 'Offer Accepted'), ('sold_and_cancelled', 'Sold and Cancelled')], help="State of the property") diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index daca2b9c4ff..cb9fd5470c5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,4 +5,79 @@ estate.property list,form + + estate.property.list + estate.property + + + + + + + + + + + + + + estate.property.view.form + estate.property + +
+ +
+

+ + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + estate.property.search + estate.property + + + + + + + + + + + + + + \ No newline at end of file From c41f21d25d1f6964b36c70c77eb4e60aeb9346ed Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Wed, 27 Aug 2025 19:10:20 +0530 Subject: [PATCH 05/14] [IMP] estate: Improvements, Formatting and added views Added property type action Added property tag action Improvements suggested by bhaumiksir(bit) Code formatting Added Comments Changed manifest file --- estate/__init__.py | 4 ++- estate/__manifest__.py | 24 ++++++-------- estate/models/__init__.py | 6 ++-- estate/models/estate_property.py | 34 +++++++++++++------- estate/models/estate_property_offer.py | 19 +++++++++++ estate/models/estate_property_tag.py | 10 ++++++ estate/models/estate_property_type.py | 10 ++++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_property_menus.xml | 16 +++++++-- estate/views/estate_property_offer_views.xml | 34 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 10 ++++++ estate/views/estate_property_type_views.xml | 10 ++++++ estate/views/estate_property_views.xml | 32 +++++++++++++++--- 13 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..d6210b1285d 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,3 @@ -from . import models \ No newline at end of file +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4e491bfc11f..e64553a09dd 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,27 +1,23 @@ -# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. { - 'name': 'ESTATE', + 'name': 'Real Estate', + 'description': 'A Module which covers all workflows related to real estate', + 'summary': 'Module to track all things related to real estate of any company', 'version': '1.0', 'category': 'Estate', - 'sequence': 15, - 'summary': 'Module to track all things related to real estate of any company', - 'description': "", - 'website': 'https://www.odoo.com/page/estate', - "application": True, 'depends': [ 'base' ], 'data': [ - "security/ir.model.access.csv", - "views/estate_property_views.xml", - "views/estate_property_menus.xml", - ], - 'demo': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_property_menus.xml', ], - "license": "LGPL-3", - 'css': [''], + 'license': 'LGPL-3', 'installable': True, 'application': True, 'auto_install': False diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 0c4a347c60d..7b28f0f82ad 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,6 @@ -# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from . import estate_property \ No newline at end of file +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 bf550e5e0b9..ce28015f780 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,12 +1,12 @@ -# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. + from datetime import datetime, timedelta from odoo import models, fields class EstateProperty(models.Model): - _name = "estate.property" - _description = "Estate Property" + _name = 'estate.property' + _description = 'Estate Property' name = fields.Char(string='Estate Property Name', required=True) description = fields.Text(string='Estate Property Description') @@ -22,16 +22,28 @@ class EstateProperty(models.Model): garden_area = fields.Integer(string='Number of Garden Area') garden_orientation = fields.Selection( string='Orientation of Garden', - selection=[('north', 'North'), - ('south', 'Sou hhjghjth'), - ('east', 'East'), - ('west', 'West')], - help="Different Types of Directions") + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ], + help='Different Types of Directions') active = fields.Boolean(default=True) state = fields.Selection( string='State', - default="new", + default='new', copy=False, required=True, - selection=[('new', 'New'), ('offer_recieved', 'Offer Received'), ('offer_accepted' , 'Offer Accepted'), ('sold_and_cancelled', 'Sold and Cancelled')], - help="State of the property") + selection=[ + ('new', 'New'), + ('offer_recieved', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold_and_cancelled', 'Sold and Cancelled') + ], + help='State of the property') + property_type_id = fields.Many2one('estate.property.type', string='Property Type Id') + buyer_id = fields.Many2one('res.users', string='Buyer', copy=False) + salesman_id = fields.Many2one('res.partner', string='Salesman', default=lambda self: self.env.user) + tag_ids = fields.Many2many('estate.property.tag', string='Estate property Tag') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='offer') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..568e8e46099 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,19 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Estate Property Offer' + + price = fields.Float(string='Offer Price') + status = fields.Selection( + string='Status Of Offer', + copy=False, + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ]) + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate.property', required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..5c9690d9d57 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,10 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Estate Property Tag' + + name = fields.Char(string='Estate Property Tag', required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..a3bc9c8e454 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,10 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Estate Property Type' + + name = fields.Char(string='Estate Property Type', required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index fd62cec345a..210d51bb44c 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,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_property_menus.xml b/estate/views/estate_property_menus.xml index 92f95bf24b6..48c3a7d9536 100644 --- a/estate/views/estate_property_menus.xml +++ b/estate/views/estate_property_menus.xml @@ -1,8 +1,18 @@ + + - - + + + + + + + + + + - \ 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..d5c79927e67 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,34 @@ + + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+ +
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..0290412427b --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,10 @@ + + + + + Property Tags + estate.property.tag + list,form + + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..0991082cfe1 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,10 @@ + + + + + Property Types + estate.property.type + list,form + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index cb9fd5470c5..74ea46ecb69 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,10 +1,14 @@ + + Properties estate.property list,form + + estate.property.list estate.property @@ -17,24 +21,29 @@ + + + + estate.property.view.form estate.property -
+ -
+

-

+
+ @@ -54,7 +63,17 @@ - + + + + + + + + + + + @@ -62,6 +81,8 @@ + + estate.property.search estate.property @@ -80,4 +101,5 @@ - \ No newline at end of file + + From 12a0164d424b49c2fc4f45330acba1740e494688 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Fri, 29 Aug 2025 11:17:14 +0530 Subject: [PATCH 06/14] [IMP] estate: validation, computed fields and onchanges in fields Added buttons for offer status Added buttons for Property status Added action for those buttons Demonstrated computed fields and onchanges fields Raised UserError exceptions for particular conditions --- estate/models/estate_property.py | 49 ++++++++++++++++++-- estate/models/estate_property_offer.py | 35 +++++++++++++- estate/views/estate_property_offer_views.xml | 20 +++++--- estate/views/estate_property_views.xml | 12 ++++- 4 files changed, 100 insertions(+), 16 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ce28015f780..a9ad85fda81 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,7 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from datetime import datetime, timedelta -from odoo import models, fields +from odoo import api, models, fields, exceptions class EstateProperty(models.Model): @@ -37,13 +37,52 @@ class EstateProperty(models.Model): required=True, selection=[ ('new', 'New'), - ('offer_recieved', 'Offer Received'), + ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), - ('sold_and_cancelled', 'Sold and Cancelled') + ('sold', 'Sold'), + ('cancel', 'Cancelled') ], help='State of the property') property_type_id = fields.Many2one('estate.property.type', string='Property Type Id') - buyer_id = fields.Many2one('res.users', string='Buyer', copy=False) - salesman_id = fields.Many2one('res.partner', string='Salesman', default=lambda self: self.env.user) + buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) + salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) tag_ids = fields.Many2many('estate.property.tag', string='Estate property Tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string='offer') + total_area = fields.Float(compute='_compute_total_area') + best_price = fields.Float(compute='_compute_best_price', string='Best Offer Price') + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for record in self: + record.total_area = record.garden_area + record.living_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price'), default=0.0) + + @api.onchange('garden') + def _on_change_garden(self): + for record in self: + if self.garden: + record.garden_area = 10 + record.garden_orientation = 'north' + else: + record.garden_area = False + record.garden_orientation = False + + def property_sold_action(self): + for record in self: + if record.state == 'cancel': + raise exceptions.UserError('Cancelled property can not be sold') + else: + record.state = 'sold' + return True + + def property_cancel_action(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError('Sold property can not be cancelled') + else: + record.state = 'cancel' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 568e8e46099..a01ee998b17 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from datetime import timedelta +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -9,7 +10,7 @@ class EstatePropertyOffer(models.Model): price = fields.Float(string='Offer Price') status = fields.Selection( - string='Status Of Offer', + string='Status', copy=False, selection=[ ('accepted', 'Accepted'), @@ -17,3 +18,33 @@ class EstatePropertyOffer(models.Model): ]) partner_id = fields.Many2one('res.partner', required=True) property_id = fields.Many2one('estate.property', required=True) + created_date = fields.Date(default=fields.Date.context_today, string='Created Date') + validity = fields.Integer(default=7, string='Validity (Days)') + date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline', string='Deadline Date', store='True') + + @api.depends('created_date', 'validity') + def _compute_date_deadline(self): + for record in self: + if record.created_date and record.validity: + record.date_deadline = record.created_date + timedelta(days=record.validity) + else: + record.date_deadline = False + + def _inverse_date_deadline(self): + for record in self: + if record.date_deadline and record.created_date: + record.validity = (record.date_deadline - record.created_date).days + else: + record.validity = 0 + + def offer_accepted_action(self): + for record in self: + record.status = 'accepted' + record.property_id.buyer_id = record.partner_id + record.property_id.selling_price = record.price + return True + + def offer_refused_action(self): + for record in self: + record.status = 'refused' + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index d5c79927e67..2e0cbdbf9d3 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,13 +7,17 @@ estate.property.offer - - + + + + + +

diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0241d9ff9ad..69cd01ae01d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,6 +6,7 @@ Properties estate.property list,form + {'search_default_state': True, 'search_default_current': True} @@ -13,14 +14,20 @@ estate.property.list estate.property - + + - + @@ -34,22 +41,21 @@
-

- +

- +

- + @@ -68,14 +74,14 @@ - - + + - + @@ -99,7 +105,7 @@ - + Date: Tue, 2 Sep 2025 16:10:20 +0530 Subject: [PATCH 09/14] [IMP] estate: Demonstrated inheritance functionality Demonstrated inheritance in python, model, and view Inherited res_users model Added inherited view_users_form --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 9 +++++++-- estate/models/estate_property_offer.py | 13 +++++++++++++ estate/models/res_users_inherited.py | 13 +++++++++++++ estate/views/estate_property_views.xml | 1 + estate/views/res_users_inherited_views.xml | 17 +++++++++++++++++ 7 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 estate/models/res_users_inherited.py create mode 100644 estate/views/res_users_inherited_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index be6b3cfa72e..976cfb98e5b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -16,6 +16,7 @@ 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_menus.xml', + 'views/res_users_inherited_views.xml' ], 'license': 'LGPL-3', 'installable': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 7b28f0f82ad..c4834b3bd72 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -4,3 +4,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users_inherited diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9a0fcf1da03..c6526ae53d5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -69,9 +69,9 @@ def _compute_best_price(self): record.best_price = max(record.offer_ids.mapped('price'), default=0.0) @api.onchange('garden') - def _on_change_garden(self): + def _onchange_garden(self): for record in self: - if self.garden: + if record.garden: record.garden_area = 10 record.garden_orientation = 'north' else: @@ -99,3 +99,8 @@ def check_offer_price(self): for record in self: if record.offer_ids.price < (0.9 * record.expected_price): raise ValidationError(f'Selling price ({record.selling_price}) should be greater than 90% ({0.9 * record.expected_price}) of the expected price ({record.expected_price})') + + @api.ondelete(at_uninstall=False) + def _unlink_if_user_state(self): + if any(user.state not in ['new', 'cancel'] for user in self): + raise UserError('Can\'t delete property which is in offer received or offer accepted or sold state.') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9cb11600dcc..4e89b264770 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -60,3 +60,16 @@ def offer_refused_action(self): for record in self: record.status = 'refused' return True + + @api.model_create_multi + def create(self, vals): + for record in vals: + property_id = record['property_id'] + if property_id: + property = self.env['estate.property'].browse(property_id) + if property.state == 'new': + property.state = 'offer_received' + if property and property.best_price: + if record['price'] <= property.best_price: + raise UserError('Offer price must be greater than best Offer Price') + return super().create(vals) diff --git a/estate/models/res_users_inherited.py b/estate/models/res_users_inherited.py new file mode 100644 index 00000000000..a5ec873680a --- /dev/null +++ b/estate/models/res_users_inherited.py @@ -0,0 +1,13 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class ResUsersInherited(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many( + comodel_name='estate.property', + inverse_name='salesman_id', + string='Property Associated with Users', + domain=[('state', '!=', 'sold'), ('state', '!=', 'cancel')]) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 69cd01ae01d..93e95a47801 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -30,6 +30,7 @@ +
diff --git a/estate/views/res_users_inherited_views.xml b/estate/views/res_users_inherited_views.xml new file mode 100644 index 00000000000..160856d4d75 --- /dev/null +++ b/estate/views/res_users_inherited_views.xml @@ -0,0 +1,17 @@ + + + + + res.users.inherited.form.inherit + res.users + + + + + + + + + + + From bac8ba3b336119154d3ee58795e190b796c220b1 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Wed, 3 Sep 2025 14:17:20 +0530 Subject: [PATCH 10/14] [ADD] estate_property: Added estate_property module to demonstrate link module Added estate_property_module Added dependency of estate and account module in estate_property module Overridden the sold action of the estate module and added it to estate_property Fixed one issue by using the round method for rounding off a number to 2 digits --- estate/models/estate_property.py | 2 +- estate/models/estate_property_offer.py | 2 +- estate/models/estate_property_type.py | 2 +- estate_property/__init__.py | 3 +++ estate_property/__manifest__.py | 21 ++++++++++++++++++ estate_property/models/__init__.py | 3 +++ estate_property/models/estate_property.py | 26 +++++++++++++++++++++++ 7 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 estate_property/__init__.py create mode 100644 estate_property/__manifest__.py create mode 100644 estate_property/models/__init__.py create mode 100644 estate_property/models/estate_property.py diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c6526ae53d5..bdc7013c71e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -98,7 +98,7 @@ def property_cancel_action(self): def check_offer_price(self): for record in self: if record.offer_ids.price < (0.9 * record.expected_price): - raise ValidationError(f'Selling price ({record.selling_price}) should be greater than 90% ({0.9 * record.expected_price}) of the expected price ({record.expected_price})') + raise ValidationError(f'Selling price ({record.selling_price}) should be greater than 90% ({round(0.9 * record.expected_price, 2)}) of the expected price ({record.expected_price})') @api.ondelete(at_uninstall=False) def _unlink_if_user_state(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4e89b264770..32f6ca9f935 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -48,7 +48,7 @@ def offer_accepted_action(self): for record in self: existing_offer = self.search([('property_id', '=', record.property_id.id), ('status', '=', 'accepted')]) if existing_offer: - raise UserError("This property already has an accepted offer.") + raise UserError('This property already has an accepted offer.') property_record = record.property_id property_record.selling_price = record.price property_record.buyer_id = record.partner_id diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 30e50554f3a..b1a06e308e7 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -10,7 +10,7 @@ class EstatePropertyType(models.Model): name = fields.Char(string='Estate Property Type', required=True) property_ids = fields.One2many('estate.property', 'property_type_id') - sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") + sequence = fields.Integer('Sequence', default=1, help='Used to order stages. Lower is better.') offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='offer') offer_count = fields.Char(string='Total numbers of offers', compute='_compute_offer_count') diff --git a/estate_property/__init__.py b/estate_property/__init__.py new file mode 100644 index 00000000000..d6210b1285d --- /dev/null +++ b/estate_property/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/estate_property/__manifest__.py b/estate_property/__manifest__.py new file mode 100644 index 00000000000..d9fe81e05c1 --- /dev/null +++ b/estate_property/__manifest__.py @@ -0,0 +1,21 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +{ + 'name': "Estate Property", + 'description': 'Real Estate Property Module which will used to help users regarding real estate properties', + 'version': '1.0', + 'category': 'Category', + 'depends': [ + 'base', + 'estate', + 'account' + ], + 'author': "Sanket Tank", + # data files always loaded at installation + 'data': [], + 'license': 'LGPL-3', + 'installable': True, + 'application': True, + 'auto_install': False +} diff --git a/estate_property/models/__init__.py b/estate_property/models/__init__.py new file mode 100644 index 00000000000..09b94f90f8d --- /dev/null +++ b/estate_property/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import estate_property diff --git a/estate_property/models/estate_property.py b/estate_property/models/estate_property.py new file mode 100644 index 00000000000..ec38122fabb --- /dev/null +++ b/estate_property/models/estate_property.py @@ -0,0 +1,26 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, Command + + +class EstateProperty(models.Model): + _inherit = 'estate.property' + + def property_sold_action(self): + self.env['account.move'].sudo().create({ + 'partner_id': self.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create({ + 'name': '6% of the selling price', + 'quantity': 1, + 'price_unit': round(self.selling_price * 0.06, 2) + }), + Command.create({ + 'name': 'an additional 100.00 from administrative fees', + 'quantity': 1, + 'price_unit': '100.00' + }) + ] + }) + return super().property_sold_action() From 521943bc5befb624a176a80d016aea0bd4c62d9d Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Wed, 3 Sep 2025 18:37:18 +0530 Subject: [PATCH 11/14] [IMP] estate: Added kanban view Added kanban view of estate property Added default group by filter in Kanban view --- estate/models/estate_property.py | 2 +- estate/views/estate_property_views.xml | 49 ++++++++++++++++++++------ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bdc7013c71e..441a4a84c8e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -48,7 +48,7 @@ class EstateProperty(models.Model): property_type_id = fields.Many2one('estate.property.type', string='Property Type Id') buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) salesman_id = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) - tag_ids = fields.Many2many('estate.property.tag', string='Estate property Tag') + tag_ids = fields.Many2many('estate.property.tag', string='Tags') offer_ids = fields.One2many('estate.property.offer', 'property_id', string='offer') total_area = fields.Float(compute='_compute_total_area') best_price = fields.Float(compute='_compute_best_price', string='Best Offer Price') diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 93e95a47801..f43039fe365 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,7 +5,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_state': True, 'search_default_current': True} @@ -14,29 +14,28 @@ estate.property.list estate.property - + > - + - - + estate.property.view.form estate.property @@ -51,11 +50,11 @@

-


+ @@ -96,6 +95,36 @@
+ + + estate_property.view.kanban + estate.property + + + + + + + + +
Expected Price: +
+
+ Best Price: +
+
+ Selling Price: +
+
+ +
+
+
+
+
+
+ estate.property.search From ada5e5ae942000947575acff0151865abc77daf1 Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Thu, 4 Sep 2025 14:10:02 +0530 Subject: [PATCH 12/14] [IMP] estate: Improved code quality Added Python docstrings Removed Code smells Changed one file name as per coding guidelines --- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 58 ++++++++++++++++++- estate/models/estate_property_offer.py | 56 ++++++++++++++++++ estate/models/estate_property_type.py | 4 ++ .../{res_users_inherited.py => res_users.py} | 2 +- estate/views/estate_property_offer_views.xml | 12 ++-- estate/views/estate_property_tag_views.xml | 3 +- estate/views/estate_property_type_views.xml | 3 +- estate/views/estate_property_views.xml | 6 +- estate/views/res_users_inherited_views.xml | 1 + .../__init__.py | 0 .../__manifest__.py | 4 +- .../models/__init__.py | 0 .../models/estate_property.py | 0 14 files changed, 134 insertions(+), 17 deletions(-) rename estate/models/{res_users_inherited.py => res_users.py} (90%) rename {estate_property => estate_account}/__init__.py (100%) rename {estate_property => estate_account}/__manifest__.py (73%) rename {estate_property => estate_account}/models/__init__.py (100%) rename {estate_property => estate_account}/models/estate_property.py (100%) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c4834b3bd72..f7e4cc6f3dd 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -4,4 +4,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer -from . import res_users_inherited +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 441a4a84c8e..0554b1a041c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -60,16 +60,35 @@ class EstateProperty(models.Model): @api.depends('living_area', 'garden_area') def _compute_total_area(self): + """ + This will compute total area whenever living area and garden area is changed + and count total area in for the record + """ + for record in self: record.total_area = record.garden_area + record.living_area @api.depends('offer_ids.price') def _compute_best_price(self): + """ + This will compute best offer price among all offers + """ + for record in self: record.best_price = max(record.offer_ids.mapped('price'), default=0.0) @api.onchange('garden') def _onchange_garden(self): + """ + Handles changes to the `garden` field. + - If `garden` is checked (True), sets default values: + - `garden_area` to 10 + - `garden_orientation` to `north` + - If `garden` is unchecked (False), clears both fields: + - `garden_area` and `garden_orientation` are set to False + This method is intended to be used as an onchange handler in Odoo views. + """ + for record in self: if record.garden: record.garden_area = 10 @@ -79,6 +98,16 @@ def _onchange_garden(self): record.garden_orientation = False def property_sold_action(self): + """ + Sold action for property + + Raises: + UserError: If the property already sold + + Returns: + bool: True if operation is successful. + """ + for record in self: if record.state == 'cancel': raise UserError('Cancelled property can not be sold') @@ -87,6 +116,16 @@ def property_sold_action(self): return True def property_cancel_action(self): + """ + Cancel action for property + + Raises: + UserError: If the property already cancelled + + Returns: + bool: True if operation is successful. + """ + for record in self: if record.state == 'sold': raise UserError('Sold property can not be cancelled') @@ -96,11 +135,26 @@ def property_cancel_action(self): @api.constrains('selling_price') def check_offer_price(self): + """ + This python constraint will check that selling price of the property + should be greater than 90 % of expected price of the property + + Raises: + ValidationError: If selling price of property is less than + 90% of expected price of the property + """ for record in self: - if record.offer_ids.price < (0.9 * record.expected_price): + if record.selling_price and record.selling_price < (0.9 * record.expected_price): raise ValidationError(f'Selling price ({record.selling_price}) should be greater than 90% ({round(0.9 * record.expected_price, 2)}) of the expected price ({record.expected_price})') @api.ondelete(at_uninstall=False) def _unlink_if_user_state(self): + """ + This validation will be checked for deletion of the property and + it will be deleted if property state should not be in `new` or `cancel` + + Raises: + UserError: If property is in `offer_received` or `offer_accepted` or `sold` state. + """ if any(user.state not in ['new', 'cancel'] for user in self): - raise UserError('Can\'t delete property which is in offer received or offer accepted or sold state.') + raise UserError('You can\'t delete property which is in offer received or offer accepted or sold state.') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 32f6ca9f935..b4364c3dc77 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -31,6 +31,15 @@ class EstatePropertyOffer(models.Model): @api.depends('created_date', 'validity') def _compute_date_deadline(self): + """ + Computes the deadline date for each offer record based on its creation date and validity period. + + If both 'created_date' and 'validity' are set, 'date_deadline' is calculated by adding the validity (in days) + to the creation date. Otherwise, 'date_deadline' is set to False. + + This method is triggered automatically when either 'created_date' or 'validity' fields are changed. + """ + for record in self: if record.created_date and record.validity: record.date_deadline = record.created_date + timedelta(days=record.validity) @@ -38,6 +47,15 @@ def _compute_date_deadline(self): record.date_deadline = False def _inverse_date_deadline(self): + """ + Inverse method for computing the 'validity' field based on 'date_deadline' and 'created_date'. + + This method updates the 'validity' field for each record by calculating the number of days + between 'date_deadline' and 'created_date'. If either field is missing, 'validity' is set to 0. + + Used as an inverse function in computed fields to allow updating 'validity' when 'date_deadline' changes. + """ + for record in self: if record.date_deadline and record.created_date: record.validity = (record.date_deadline - record.created_date).days @@ -45,7 +63,23 @@ def _inverse_date_deadline(self): record.validity = 0 def offer_accepted_action(self): + """ + Accepts an offer for a property, ensuring only one accepted offer per property. + + Iterates through each offer record, checks if there is already an accepted offer for the same property. + If an accepted offer exists, raises a UserError to prevent multiple accepted offers. + Otherwise, updates the property's selling price, buyer, and state to reflect the accepted offer, + and sets the offer's status to 'accepted'. + + Raises: + UserError: If the property already has an accepted offer. + + Returns: + bool: True if the operation is successful. + """ + for record in self: + # breakpoint() existing_offer = self.search([('property_id', '=', record.property_id.id), ('status', '=', 'accepted')]) if existing_offer: raise UserError('This property already has an accepted offer.') @@ -57,12 +91,34 @@ def offer_accepted_action(self): return True def offer_refused_action(self): + """ + This action used to refuse an offer. + + Returns: + bool: True when operation is successful. + """ for record in self: record.status = 'refused' return True @api.model_create_multi def create(self, vals): + """ + Creates new estate property offers and updates the related property's state and best price validation. + + For each offer in `vals`: + - If the related property's state is `new`, it is updated to `offer_received`. + - If the offer price is less than or equal to the property's best price, a UserError is raised. + + Args: + vals (list of dict): List of values for new estate property offers. + + Returns: + recordset: The newly created estate property offer records. + + Raises: + UserError: If the offer price is not greater than the property's best price. + """ for record in vals: property_id = record['property_id'] if property_id: diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index b1a06e308e7..bf107b744af 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -16,5 +16,9 @@ class EstatePropertyType(models.Model): @api.depends('offer_ids') def _compute_offer_count(self): + """ + Counts total number of offers which is offered to a property + """ + for record in self: record.offer_count = len(record.offer_ids) diff --git a/estate/models/res_users_inherited.py b/estate/models/res_users.py similarity index 90% rename from estate/models/res_users_inherited.py rename to estate/models/res_users.py index a5ec873680a..e820eaea194 100644 --- a/estate/models/res_users_inherited.py +++ b/estate/models/res_users.py @@ -3,7 +3,7 @@ from odoo import fields, models -class ResUsersInherited(models.Model): +class ResUsers(models.Model): _inherit = 'res.users' property_ids = fields.One2many( diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 24d49486fd0..566c7b59832 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -3,7 +3,7 @@ - Property Offers + Estate Property Offers estate.property.offer list,form [('property_type_id', '=', active_id)] @@ -13,9 +13,9 @@ - - - estate.property.offer.list + + + estate.property.offer.view.list estate.property.offer - + - estate.property.offer.form + estate.property.offer.view.form estate.property.offer diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 0290412427b..3c64026e60b 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -1,8 +1,9 @@ + - Property Tags + Estate Property Tags estate.property.tag list,form diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index dd97a6e3a19..df609fb65be 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -1,8 +1,9 @@ + - Property Types + Estate Property Types estate.property.type list,form diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f43039fe365..ff8a7e0ea7e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,15 +3,15 @@ - Properties + Estate Properties estate.property list,form,kanban {'search_default_state': True, 'search_default_current': True} - - estate.property.list + + estate.property.view.list estate.property + res.users.inherited.form.inherit res.users diff --git a/estate_property/__init__.py b/estate_account/__init__.py similarity index 100% rename from estate_property/__init__.py rename to estate_account/__init__.py diff --git a/estate_property/__manifest__.py b/estate_account/__manifest__.py similarity index 73% rename from estate_property/__manifest__.py rename to estate_account/__manifest__.py index d9fe81e05c1..5908a23e67d 100644 --- a/estate_property/__manifest__.py +++ b/estate_account/__manifest__.py @@ -2,8 +2,8 @@ { - 'name': "Estate Property", - 'description': 'Real Estate Property Module which will used to help users regarding real estate properties', + 'name': "Estate Account", + 'description': 'Real Estate Account Module which will used to help users regarding real estate properties', 'version': '1.0', 'category': 'Category', 'depends': [ diff --git a/estate_property/models/__init__.py b/estate_account/models/__init__.py similarity index 100% rename from estate_property/models/__init__.py rename to estate_account/models/__init__.py diff --git a/estate_property/models/estate_property.py b/estate_account/models/estate_property.py similarity index 100% rename from estate_property/models/estate_property.py rename to estate_account/models/estate_property.py From 763d9c3298c095308d894855ef57eff45b2d539c Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Fri, 5 Sep 2025 17:28:41 +0530 Subject: [PATCH 13/14] [IMP] awesome_owl: Demonstrated OWL Framework Added four components - Card - Counter - TodoItem - TodoList Added the respective files, which are required for the components like XML and JS --- awesome_owl/static/src/card/card.js | 11 +++++++++ awesome_owl/static/src/card/card.xml | 11 +++++++++ awesome_owl/static/src/counter/counter.js | 21 ++++++++++++++++ awesome_owl/static/src/counter/counter.xml | 9 +++++++ awesome_owl/static/src/playground.js | 16 ++++++++++++- awesome_owl/static/src/playground.xml | 11 +++++++++ awesome_owl/static/src/todo_list/todo_item.js | 13 ++++++++++ .../static/src/todo_list/todo_item.xml | 9 +++++++ awesome_owl/static/src/todo_list/todo_list.js | 24 +++++++++++++++++++ .../static/src/todo_list/todo_list.xml | 13 ++++++++++ 10 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 awesome_owl/static/src/card/card.js create mode 100644 awesome_owl/static/src/card/card.xml create mode 100644 awesome_owl/static/src/counter/counter.js create mode 100644 awesome_owl/static/src/counter/counter.xml create mode 100644 awesome_owl/static/src/todo_list/todo_item.js create mode 100644 awesome_owl/static/src/todo_list/todo_item.xml create mode 100644 awesome_owl/static/src/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/todo_list/todo_list.xml diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js new file mode 100644 index 00000000000..bfcc7313dca --- /dev/null +++ b/awesome_owl/static/src/card/card.js @@ -0,0 +1,11 @@ +/** @odoo-module */ + +import { Component } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.Card"; + static props = { + title: String, + content: String, + }; +} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml new file mode 100644 index 00000000000..23878b0723a --- /dev/null +++ b/awesome_owl/static/src/card/card.xml @@ -0,0 +1,11 @@ + + + +
+
+
+

+
+
+
+
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..e7484e880d9 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,21 @@ +/** @odoo-module */ + +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.Counter"; + static props = { + onChange: { type: Function, optional: true } + }; + + setup() { + this.state = useState({ value: 1 }); + } + + increment() { + this.state.value = this.state.value + 1; + if (this.props.onChange) { + this.props.onChange(); + } + } +} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..4791fb6cd42 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,9 @@ + + + +
+ Counter: + +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07bb..7907653987d 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 "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo_list/todo_list" export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, TodoList }; + + setup() { + this.str1 = "
some content
"; + this.str2 = markup("
some content
"); + this.sum = useState({ value: 2 }); + } + + incrementSum() { + this.sum.value++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..9b5a175a0e5 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -4,6 +4,17 @@
hello world + + +
The sum is: +
+
+
+ + +
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js new file mode 100644 index 00000000000..164ced55c07 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,13 @@ +/** @odoo-module */ + +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.TodoItem"; + static props = { + todo: { + type: Object, + shape: { id: Number, description: String, isCompleted: Boolean } + } + }; +} diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml new file mode 100644 index 00000000000..3ae8b5e883b --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,9 @@ + + + +
+ . + +
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..5fc367c8b2d --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,24 @@ +/** @odoo-module */ + +import { Component, useState } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + static components = { TodoItem }; + + setup() { + this.todos = useState([]); + this.newId = 1; + } + addTodo(e) { + if (e.keyCode === 13 && e.target.value != "") { + this.todos.push({ + id: this.newId++, + description: e.target.value, + isCompleted: false + }) + e.target.value = "" + } + } +} diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..dccfc4bee98 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,13 @@ + + + +
+ +
+ + + +
+
+
+
From d0e37e7ca07a5baf87eb52d10419f06a582ee71f Mon Sep 17 00:00:00 2001 From: "Sanket Tank(stan)" Date: Mon, 8 Sep 2025 14:18:36 +0530 Subject: [PATCH 14/14] [IMP] awesome_owl: Demonstrated owl life cycle, slots and custom hooks Used Onmounted lifecycle method Used the useRef Hook Made a new custom hook called useAutofocus Modified Card, TodoList, TodoItem Components Added functionality of add todo, delete todo, and auto focus behaviour of input --- awesome_owl/static/src/card/card.js | 17 +++++++++++++++-- awesome_owl/static/src/card/card.xml | 11 +++++++++-- awesome_owl/static/src/playground.xml | 8 ++++++-- awesome_owl/static/src/todo_list/todo_item.js | 12 +++++++++++- awesome_owl/static/src/todo_list/todo_item.xml | 10 +++++++--- awesome_owl/static/src/todo_list/todo_list.js | 18 ++++++++++++++++++ awesome_owl/static/src/todo_list/todo_list.xml | 4 ++-- awesome_owl/static/src/utils.js | 9 +++++++++ 8 files changed, 77 insertions(+), 12 deletions(-) create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js index bfcc7313dca..5276477fbba 100644 --- a/awesome_owl/static/src/card/card.js +++ b/awesome_owl/static/src/card/card.js @@ -1,11 +1,24 @@ /** @odoo-module */ -import { Component } from "@odoo/owl"; +import { Component, useState } from "@odoo/owl"; export class Card extends Component { static template = "awesome_owl.Card"; static props = { title: String, - content: String, + slots: { + type: Object, + shape: { + default: true + }, + }, }; + + setup() { + this.state = useState({ isOpen: true }) + } + + toggle() { + this.state.isOpen=!this.state.isOpen + } } diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml index 23878b0723a..5998f9df214 100644 --- a/awesome_owl/static/src/card/card.xml +++ b/awesome_owl/static/src/card/card.xml @@ -3,8 +3,15 @@
-
-

+
+
+
+ +
+
+

+ +

diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 9b5a175a0e5..d019294b868 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -10,8 +10,12 @@
- - + + content of card 1 + + + +
diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js index 164ced55c07..f60063d2510 100644 --- a/awesome_owl/static/src/todo_list/todo_item.js +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -8,6 +8,16 @@ export class TodoItem extends Component { todo: { type: Object, shape: { id: Number, description: String, isCompleted: Boolean } - } + }, + toggleState: Function, + deleteState: Function, }; + + onChange() { + this.props.toggleState(this.props.todo.id); + } + + onDelete(){ + this.props.deleteState(this.props.todo.id) + } } diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml index 3ae8b5e883b..ca9a1a4cbfd 100644 --- a/awesome_owl/static/src/todo_list/todo_item.xml +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -1,9 +1,13 @@ -
- . - +
+ + +
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js index 5fc367c8b2d..129de2277cd 100644 --- a/awesome_owl/static/src/todo_list/todo_list.js +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -1,6 +1,7 @@ /** @odoo-module */ import { Component, useState } from "@odoo/owl"; +import { useAutoFocus } from "../utils"; import { TodoItem } from "./todo_item"; export class TodoList extends Component { @@ -8,9 +9,11 @@ export class TodoList extends Component { static components = { TodoItem }; setup() { + useAutoFocus('input') this.todos = useState([]); this.newId = 1; } + addTodo(e) { if (e.keyCode === 13 && e.target.value != "") { this.todos.push({ @@ -21,4 +24,19 @@ export class TodoList extends Component { e.target.value = "" } } + + toggleTodo(todoId) { + const todo = this.todos.find((todo) => todo.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + deleteTodo(todoId) { + const index = this.todos.findIndex((todo) => todo.id === todoId); + if (index >= 0) { + this.todos.splice(index, 1); + } + } + } diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml index dccfc4bee98..d5edd45875b 100644 --- a/awesome_owl/static/src/todo_list/todo_list.xml +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -2,10 +2,10 @@
- +
- +
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..918f0b1d647 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,9 @@ +import { onMounted, useRef } from "@odoo/owl"; + +export function useAutoFocus(elementName) { + const ref = useRef(elementName) + onMounted(() => { + ref.el.focus() + }) + +}