From 152f2328de512603f068e1da42436388bba76c10 Mon Sep 17 00:00:00 2001 From: habar Date: Tue, 10 Mar 2026 19:06:07 +0530 Subject: [PATCH 01/11] [ADD] estate: add initial module and property model Initialize the real estate addon with a proper manifest and define the core 'estate.property' model. This provides the foundation for managing property listings, including essential fields like price, area, and availability. --- estate/__init__.py | 1 + estate/__manifest__.py | 12 ++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 20 ++++++++++++++++++++ 4 files changed, 34 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..9c14ad3b6fc --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': "estate", + 'version': '1.0', + 'depends': ['base'], + 'author': "Harshvardhan", + 'category': 'Category', + 'description': """ + This is the sample module for practise + """, + 'application': True, + 'license': 'LGPL-3', +} 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..e408a012f7d --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,20 @@ +from odoo import models, fields + +class EstateProperty(models.Model): + _name = 'estate.property' + _description = 'Real Estate Property' + + name = fields.Char(string="Name", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + date_availability = fields.Date(string="Date") + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bedrooms") + 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'), ('east', 'East'), ('west', 'West'), ('south', 'South')]) + \ No newline at end of file From 2ea6100ffcd37d9e97ec71314cbde15b38594496 Mon Sep 17 00:00:00 2001 From: habar Date: Wed, 11 Mar 2026 18:32:14 +0530 Subject: [PATCH 02/11] [ADD] estate: define security access rights Implement the security layer for the estate module. Added ir.model.access.csv to grant CRUD permissions to the standard user groups, ensuring properties and offers are manageable through the UI. --- estate/__manifest__.py | 10 +++++++--- estate/models/estate_property.py | 11 ++++++++--- estate/security/ir.model.access.csv | 2 ++ 3 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9c14ad3b6fc..05273d27a5d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,11 +2,15 @@ 'name': "estate", 'version': '1.0', 'depends': ['base'], - 'author': "Harshvardhan", - 'category': 'Category', + 'author': "habar", + 'category': 'Tutorials', 'description': """ - This is the sample module for practise + This is the sample module for practise. """, + 'data': [ + 'security/ir.model.access.csv', + ], 'application': True, 'license': 'LGPL-3', + 'installable': True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e408a012f7d..268ee66e8e0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,10 @@ from odoo import models, fields + class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate Property' - + name = fields.Char(string="Name", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") @@ -16,5 +17,9 @@ class EstateProperty(models.Model): garage = fields.Boolean(string="Garage") garden = fields.Boolean(string="Garden") garden_area = fields.Integer(string="Garden Area") - garden_orientation = fields.Selection([('north', 'North'), ('east', 'East'), ('west', 'West'), ('south', 'South')]) - \ No newline at end of file + garden_orientation = fields.Selection([ + ('north', 'North'), + ('east', 'East'), + ('west', 'West'), + ('south', 'South') + ]) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /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 \ No newline at end of file From e2c526f9880cf32bb9a8c4d4808cc0a9da1bc3cf Mon Sep 17 00:00:00 2001 From: habar Date: Thu, 12 Mar 2026 18:46:41 +0530 Subject: [PATCH 03/11] [ADD] estate: add actions and menus for property model Introduce the UI entry points for the real estate module. Defined the window action and menu hierarchy to allow users to view and manage property records from the web interface. --- estate/__manifest__.py | 4 +++- estate/models/estate_property.py | 23 ++++++++++++++++------- estate/views/estate_menus.xml | 8 ++++++++ estate/views/estate_property_views.xml | 24 ++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 05273d27a5d..59f3f604002 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,5 @@ { - 'name': "estate", + 'name': "Estate", 'version': '1.0', 'depends': ['base'], 'author': "habar", @@ -9,6 +9,8 @@ """, 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], 'application': True, 'license': 'LGPL-3', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 268ee66e8e0..d5a08f3a51b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,25 +1,34 @@ from odoo import models, fields +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate Property' - - name = fields.Char(string="Name", required=True) + + name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Date") + date_availability = fields.Date(string="Available From", copy=False, default=fields.Date.today() + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price") - bedrooms = fields.Integer(string="Bedrooms") - living_area = fields.Integer(string="Living Area") + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer(string="Bedrooms", default=2) + living_area = fields.Integer(string="Living Area (spm)") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") garden = fields.Boolean(string="Garden") - garden_area = fields.Integer(string="Garden Area") + garden_area = fields.Integer(string="Garden Area (spm)") garden_orientation = fields.Selection([ ('north', 'North'), ('east', 'East'), ('west', 'West'), ('south', 'South') ]) + active = fields.Boolean(string="Active", default=True) + state = fields.Selection([ + ('new', 'New'), + ('offerreceived', 'Offer Received'), + ('offeraccepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], string="State", copy=False, default='new') diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..99abf1350f5 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..35db6dafd91 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,24 @@ + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + Properties + estate.property + list,form + + From 16566bf9e17ff607b43d0d56fc6cddd0db6326e1 Mon Sep 17 00:00:00 2001 From: habar Date: Mon, 16 Mar 2026 18:45:04 +0530 Subject: [PATCH 04/11] [IMP] estate: add form and search views Introduce custom layouts for property management. The form view provides a structured data entry interface, while the search view enables filtering by key attributes like price and status. --- estate/__manifest__.py | 6 +-- estate/models/estate_property.py | 8 ++-- estate/security/ir.model.access.csv | 2 +- estate/views/estate_property_views.xml | 56 ++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 59f3f604002..c238e0514cd 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,9 +8,9 @@ This is the sample module for practise. """, 'data': [ - 'security/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_menus.xml', + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], 'application': True, 'license': 'LGPL-3', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d5a08f3a51b..7aba45c36e2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import models, fields from dateutil.relativedelta import relativedelta +from odoo import fields, models class EstateProperty(models.Model): @@ -11,7 +11,7 @@ class EstateProperty(models.Model): postcode = fields.Char(string="Postcode") date_availability = fields.Date(string="Available From", copy=False, default=fields.Date.today() + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + selling_price = fields.Float(string="Selling Price", copy=False) bedrooms = fields.Integer(string="Bedrooms", default=2) living_area = fields.Integer(string="Living Area (spm)") facades = fields.Integer(string="Facades") @@ -27,8 +27,8 @@ class EstateProperty(models.Model): active = fields.Boolean(string="Active", default=True) state = fields.Selection([ ('new', 'New'), - ('offerreceived', 'Offer Received'), - ('offeraccepted', 'Offer Accepted'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled') ], string="State", copy=False, default='new') diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..32389642d4f 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +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 \ No newline at end of file +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 35db6dafd91..14e416db34c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,44 @@ + + estate.property.form + estate.property + +
+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ estate.property.list estate.property @@ -16,6 +55,23 @@ + + estate.property.search + estate.property + + + + + + + + + + + + + + Properties estate.property From 9b0819798148a65bb22ee56e7864ef461850e3cf Mon Sep 17 00:00:00 2001 From: habar Date: Tue, 17 Mar 2026 18:59:42 +0530 Subject: [PATCH 05/11] [ADD] estate: implement relational models and data mapping Introduce core relations for property management. Added property types, tags, and offer tracking to support complex data structures. This enables interaction through Many2one, Many2many, and One2many relationships. This completes Chapter 7 by linking properties with their associated types, tags, and offers. --- estate/__manifest__.py | 3 +++ estate/models/__init__.py | 3 +++ estate/models/estate_property.py | 25 +++++++++++-------- estate/models/estate_property_offer.py | 14 +++++++++++ estate/models/estate_property_tag.py | 8 ++++++ estate/models/estate_property_type.py | 8 ++++++ estate/security/ir.model.access.csv | 3 +++ estate/views/estate_menus.xml | 4 +++ estate/views/estate_property_offer_views.xml | 26 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 8 ++++++ estate/views/estate_property_type_views.xml | 8 ++++++ estate/views/estate_property_views.xml | 25 +++++++++++++------ 12 files changed, 117 insertions(+), 18 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/__manifest__.py b/estate/__manifest__.py index c238e0514cd..4f106ce9382 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_menus.xml', ], 'application': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7aba45c36e2..fc9b7e100f5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,7 +5,7 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Real Estate Property' - + name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") @@ -19,16 +19,21 @@ class EstateProperty(models.Model): garden = fields.Boolean(string="Garden") garden_area = fields.Integer(string="Garden Area (spm)") garden_orientation = fields.Selection([ - ('north', 'North'), - ('east', 'East'), - ('west', 'West'), - ('south', 'South') + ('north', "North"), + ('east', "East"), + ('west', "West"), + ('south', "South") ]) active = fields.Boolean(string="Active", default=True) state = fields.Selection([ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled') + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled") ], string="State", copy=False, default='new') + property_type_id = fields.Many2one('estate.property.type', string="Property Type", ondelete="cascade") + sales_person_id = fields.Many2one('res.users', string='Salesman', ondelete='cascade') + buyer_id = fields.Many2one('res.partner', string='Buyer', ondelete='cascade') + property_tag_ids = fields.Many2many('estate.property.tag') + offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..aacaf0e29a0 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,14 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Offer" + + price = fields.Float(string='Price') + status = fields.Selection([ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], string='Status', copy=False) + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate.property', required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..01da11e0ec2 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Real Estate Tag' + + name = fields.Char(string='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..1f90b1c59e2 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate property type" + + name = fields.Char(string="Type", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 32389642d4f..89f97c50842 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 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 99abf1350f5..a40ec4a5649 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,5 +4,9 @@ + + + +
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..88694b2f445 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,26 @@ + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + +
+ + + estate.property.offer.list + 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..4dcd3674e34 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,8 @@ + + + + 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..dd480955e4e --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,8 @@ + + + + Property Type + estate.property.type + list,form + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 14e416db34c..031276ee7a0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -7,16 +7,16 @@

+

- - - - - - - - + + + + + + + @@ -33,6 +33,15 @@ + + + + + + + + +
From 3d53ec5db86f445cc0fe39e297d3e33f09b3602f Mon Sep 17 00:00:00 2001 From: habar Date: Tue, 24 Mar 2026 18:17:37 +0530 Subject: [PATCH 06/11] [IMP] estate: added computed field automatic area calculation Add a new field that automatically calculates the total property area. By linking the living area and garden area, the system now updates the total size whenever either value changes. This removes the need for manual entry and prevents mistakes. --- estate/models/estate_property.py | 10 ++++++++-- estate/views/estate_property_views.xml | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index fc9b7e100f5..2076549bbbe 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -9,7 +9,7 @@ class EstateProperty(models.Model): name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Available From", copy=False, default=fields.Date.today() + relativedelta(months=3)) + date_availability = fields.Date(string="Available From", copy=False, default=lambda self: fields.Date.today() + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) selling_price = fields.Float(string="Selling Price", copy=False) bedrooms = fields.Integer(string="Bedrooms", default=2) @@ -18,6 +18,7 @@ class EstateProperty(models.Model): garage = fields.Boolean(string="Garage") garden = fields.Boolean(string="Garden") garden_area = fields.Integer(string="Garden Area (spm)") + total_area = fields.Float(string="Total Area (sqm)", compute="_computed_total_area", store=True) garden_orientation = fields.Selection([ ('north', "North"), ('east', "East"), @@ -37,3 +38,8 @@ class EstateProperty(models.Model): buyer_id = fields.Many2one('res.partner', string='Buyer', ondelete='cascade') property_tag_ids = fields.Many2many('estate.property.tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + + @api.depends("living_area", "garden_area") + def _computed_total_area(self): + for rec in self: + rec.total_area = rec.living_area + rec.garden_area diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 031276ee7a0..20b248a9b01 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -30,6 +30,7 @@ + @@ -52,7 +53,7 @@ estate.property.list estate.property - + From 3b65a2b2b92056fdc25a179d0293c883de18fe02 Mon Sep 17 00:00:00 2001 From: habar Date: Wed, 25 Mar 2026 21:53:29 +0530 Subject: [PATCH 07/11] [IMP] estate: add computed field for best price Implement business logic to automatically track the highest bid for a property. Using a computed field, the system now identifies the 'best price' from all related offers, ensuring sellers see the most competitive deal at a glance. --- estate/models/estate_property.py | 8 ++++++++ estate/views/estate_property_views.xml | 7 ++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2076549bbbe..04d155d0350 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from dateutil.relativedelta import relativedelta + from odoo import api, fields, models @@ -38,8 +39,15 @@ class EstateProperty(models.Model): buyer_id = fields.Many2one('res.partner', string='Buyer', ondelete='cascade') property_tag_ids = fields.Many2many('estate.property.tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + best_price = fields.Float(string="Best Offer", compute="_computed_best_offer", store=True) @api.depends("living_area", "garden_area") def _computed_total_area(self): for rec in self: rec.total_area = rec.living_area + rec.garden_area + + @api.depends("offer_ids.price") + def _computed_best_offer(self): + for rec in self: + prices = rec.offer_ids.mapped("price") + rec.best_price = max(prices) if prices else 0.0 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 20b248a9b01..2324e6ae535 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -16,6 +16,7 @@ + @@ -35,7 +36,7 @@ - + @@ -61,6 +62,8 @@ + + @@ -76,6 +79,8 @@ + + From 360f0abf5a16d7531ab8b9b8a622a87a67a93aa2 Mon Sep 17 00:00:00 2001 From: habar Date: Thu, 26 Mar 2026 18:38:55 +0530 Subject: [PATCH 08/11] [IMP] estate: added validity & deadline in estate property offer model. 1) Define two fields validity and deadline. 2) Create compute & inverse method for them 3) Also added search functionality in the estate property model for best offers. --- estate/models/estate_property.py | 25 +++++++++++++------- estate/models/estate_property_offer.py | 19 +++++++++++++-- estate/views/estate_property_offer_views.xml | 4 ++++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 04d155d0350..679d9ae0cdd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,18 +8,18 @@ class EstateProperty(models.Model): _description = 'Real Estate Property' name = fields.Char(string="Title", required=True) - description = fields.Text(string="Description") - postcode = fields.Char(string="Postcode") + description = fields.Text() + postcode = fields.Char() date_availability = fields.Date(string="Available From", copy=False, default=lambda self: fields.Date.today() + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) selling_price = fields.Float(string="Selling Price", copy=False) - bedrooms = fields.Integer(string="Bedrooms", default=2) + bedrooms = fields.Integer(default=2) living_area = fields.Integer(string="Living Area (spm)") - facades = fields.Integer(string="Facades") - garage = fields.Boolean(string="Garage") - garden = fields.Boolean(string="Garden") + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() garden_area = fields.Integer(string="Garden Area (spm)") - total_area = fields.Float(string="Total Area (sqm)", compute="_computed_total_area", store=True) + total_area = fields.Float(string="Total Area (sqm)", compute="_computed_total_area") garden_orientation = fields.Selection([ ('north', "North"), ('east', "East"), @@ -33,13 +33,13 @@ class EstateProperty(models.Model): ('offer_accepted', "Offer Accepted"), ('sold', "Sold"), ('cancelled', "Cancelled") - ], string="State", copy=False, default='new') + ], copy=False, default='new') property_type_id = fields.Many2one('estate.property.type', string="Property Type", ondelete="cascade") sales_person_id = fields.Many2one('res.users', string='Salesman', ondelete='cascade') buyer_id = fields.Many2one('res.partner', string='Buyer', ondelete='cascade') property_tag_ids = fields.Many2many('estate.property.tag') offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") - best_price = fields.Float(string="Best Offer", compute="_computed_best_offer", store=True) + best_price = fields.Float(string="Best Offer", compute="_computed_best_offer", search="_search_best_offer", store=False) @api.depends("living_area", "garden_area") def _computed_total_area(self): @@ -51,3 +51,10 @@ def _computed_best_offer(self): for rec in self: prices = rec.offer_ids.mapped("price") rec.best_price = max(prices) if prices else 0.0 + + def _search_best_offer(self, operator, value): + return [ + '&', + ('offer_ids.price', '>', 10000), + ('offer_ids.price', operator, value) + ] diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index aacaf0e29a0..2c08b12128c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -9,6 +11,19 @@ class EstatePropertyOffer(models.Model): status = fields.Selection([ ('accepted', 'Accepted'), ('refused', 'Refused') - ], string='Status', copy=False) + ], copy=False) partner_id = fields.Many2one('res.partner', required=True) property_id = fields.Many2one('estate.property', required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date(string="Deadline", compute="_compute_deadline_method", inverse="_inverse_deadline_method") + + @api.depends("create_date", "validity") + def _compute_deadline_method(self): + for rec in self: + create_date = rec.create_date.date() if rec.create_date else fields.Date.today() + rec.date_deadline = create_date + relativedelta(days=rec.validity) + + def _inverse_deadline_method(self): + for rec in self: + create_date = rec.create_date.date() if rec.create_date else fields.Date.today() + rec.validity = relativedelta(rec.date_deadline, create_date).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 88694b2f445..f25a16099dd 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,8 @@
+ + @@ -19,6 +21,8 @@ + + From 2429659d9f925567e6becaf8f49b7b8cccf1f663 Mon Sep 17 00:00:00 2001 From: habar Date: Fri, 27 Mar 2026 18:44:18 +0530 Subject: [PATCH 09/11] [IMP] estate: added onchange method in the estate property model 1) Define _onchange_garden() to automatically set default garden_area and garden_orientation when the garden is enabled. 2) Clear these fields when the garden is disabled. 3) Add a warning notification when the garden checkbox is checked. --- estate/models/estate_property.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 679d9ae0cdd..39d10d115fd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -14,11 +14,11 @@ class EstateProperty(models.Model): expected_price = fields.Float(string="Expected Price", required=True) selling_price = fields.Float(string="Selling Price", copy=False) bedrooms = fields.Integer(default=2) - living_area = fields.Integer(string="Living Area (spm)") + living_area = fields.Float(string="Living Area (spm)") facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer(string="Garden Area (spm)") + garden_area = fields.Float(string="Garden Area (spm)") total_area = fields.Float(string="Total Area (sqm)", compute="_computed_total_area") garden_orientation = fields.Selection([ ('north', "North"), @@ -48,9 +48,8 @@ def _computed_total_area(self): @api.depends("offer_ids.price") def _computed_best_offer(self): - for rec in self: - prices = rec.offer_ids.mapped("price") - rec.best_price = max(prices) if prices else 0.0 + prices = self.offer_ids.mapped("price") + self.best_price = max(prices) if prices else 0.0 def _search_best_offer(self, operator, value): return [ @@ -58,3 +57,20 @@ def _search_best_offer(self, operator, value): ('offer_ids.price', '>', 10000), ('offer_ids.price', operator, value) ] + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + + return { + 'warning': { + 'title': "Garden Enabled", + 'message': "Default area set to 10 and orientation north", + 'type': "notification" + } + } + else: + self.garden_area = 0 + self.garden_orientation = False From 9456a9a48ef4ef92ef4a57f7d3ab064ea9e0c595 Mon Sep 17 00:00:00 2001 From: habar Date: Mon, 30 Mar 2026 18:43:27 +0530 Subject: [PATCH 10/11] [IMP] estate: define actions & buttons Added 'Cancel' and 'Sold' buttons to the estate.property model. A sold property cannot be cancelled, and a cancelled property cannot be marked as sold. Used Odoo Exceptions to show error messages for invalid actions. --- estate/models/estate_property.py | 15 +++++++++++++++ estate/views/estate_property_views.xml | 7 ++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 39d10d115fd..17be0a2f28c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -74,3 +75,17 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + def set_property_cancelled(self): + for rec in self: + if rec.state == 'cancelled': + raise UserError("Sold property can not be cancelled.") + rec.state = 'cancelled' + return True + + def set_property_sold(self): + for rec in self: + if rec.state == 'cancelled': + raise UserError("Cancelled properties can not be sold.") + rec.state = 'sold' + return True diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2324e6ae535..6c0cfaed397 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,11 +5,16 @@ estate.property
+
+ + +

+ @@ -81,7 +86,7 @@ - + From 176a67783cd66252625ff3ae7040545a37e08872 Mon Sep 17 00:00:00 2001 From: habar Date: Tue, 31 Mar 2026 18:10:49 +0530 Subject: [PATCH 11/11] [IMP] estate: added methods to the estate property offer This functionality define to accept & refuse offer once offer is accepted it set selling price and buyer to the corresponding property. generates error if more than one offer is try to accept, added buttons to the corresponding actions used 'checked' & 'unchecked' icons. --- estate/models/estate_property_offer.py | 17 +++++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ 2 files changed, 19 insertions(+) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 2c08b12128c..78b2a5e7272 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -27,3 +28,19 @@ def _inverse_deadline_method(self): for rec in self: create_date = rec.create_date.date() if rec.create_date else fields.Date.today() rec.validity = relativedelta(rec.date_deadline, create_date).days + + def action_accept_offer(self): + for rec in self: + if any(i.status == "accepted" for i in rec.property_id.offer_ids): + raise UserError("Offer already accepted for given property.") + + rec.status = "accepted" + rec.property_id.buyer_id = rec.partner_id.id + rec.property_id.selling_price = rec.price + rec.property_id.state = "offer_accepted" + return True + + def action_refuse_offer(self): + for rec in self: + rec.status = "refused" + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index f25a16099dd..c0afbad9e16 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -23,6 +23,8 @@ +