From eff5466368a89e1617e00f177cef292b2e8cb4d7 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Thu, 2 Apr 2026 18:00:57 +0530 Subject: [PATCH 01/30] [ADD] estate: Added the new real estate module Created a new folder called 'estate' with '__init__.py' (empty) and '__manifest__.py'. Ensured the app is listed in the Apps module, the only dependency added was 'base'. Chapter 2 --- estate/__init__.py | 0 estate/__manifest__.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..f9de351583a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + + +{ + 'name': 'Estate', + 'category': 'Real Estate', + 'sequence': 1, + 'summary': 'Making the real estate app from tutorials', + 'website': 'www.google.com', + 'depends': [ + 'base', + ], + 'installable': True, + 'application': True, + 'author': 'Rini Pillai', +} From d51a3569bca14c1625982dd754c94128bd63d8b3 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 3 Apr 2026 10:12:57 +0530 Subject: [PATCH 02/30] [CLN] estate: Code cleanup in the new real estate module Removed the unnecessary encoding declaration and removed trailing whitespaces from multiple lines in the '__manifest__.py' file. Also, added the missing extra line in the end of the code file Chapter 2 --- estate/__manifest__.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f9de351583a..37a0bac046f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,17 +1,14 @@ -# -*- coding: utf-8 -*- -# Part of Odoo. See LICENSE file for full copyright and licensing details. - - -{ +{ 'name': 'Estate', 'category': 'Real Estate', 'sequence': 1, 'summary': 'Making the real estate app from tutorials', - 'website': 'www.google.com', + 'website': 'www.odoo.com', 'depends': [ 'base', ], 'installable': True, - 'application': True, + 'application': True, 'author': 'Rini Pillai', } + From 365414a33b9fe99eb5de0a302fa6052e634f2b85 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 3 Apr 2026 12:33:31 +0530 Subject: [PATCH 03/30] [ADD] estate: Defined the estate properties model with basic fields Created the estate_properties model with simple fields for name, description, date_availability, etc. Understood imports, models, and fields. Chapter 3 --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_properties.py | 21 +++++++++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_properties.py diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..c74fc2fd8eb --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_properties diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py new file mode 100644 index 00000000000..5c33551308a --- /dev/null +++ b/estate/models/estate_properties.py @@ -0,0 +1,21 @@ +from odoo import models, fields + +class EstateProperties(models.Model): + _name = "estate.properties" + _description = "Real Estate Properties" + # _log_access = False + + name = fields.Char('Property Name', required=True, help="Name of the property shown") + description = fields.Text('Description', help="Description of the property shown") + postcode = fields.Char('Postcode', help="Postal Code of the property shown") + date_availability = fields.Date('Availability Date', help="Date of availability of the property shown") + expected_price = fields.Float('Expected Price', required=True, help="Expected price of the property shown") + selling_price = fields.Float('Selling Price', help="Selling Price of the property shown") + bedrooms = fields.Integer('Bedrooms', help="Number of bedrooms in the property shown") + living_area = fields.Integer('Living Area', help="Number of living rooms in the property shown") + facades = fields.Integer('Facades', help="Number of facades in the property shown") + garage = fields.Boolean('Has Garage?', help="Does the proeprty have a garage?") + garden = fields.Boolean('Has Garden?', help="Does the property have a garden?") + garden_area = fields.Integer('Garden Area', help="Area of the garden of the property shown") + garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Directional orientation of the garden of the property shown") + From cbdd03e3842dca6015113c2592e2e2e0b142011c Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 3 Apr 2026 18:04:14 +0530 Subject: [PATCH 04/30] [ADD] estate: Setting Access Rights for the estate module Created the security folder inside the estate module with the csv file giving read, write, create and unlink access to the group 'base.group_user' and ensured the no access rights warning doesn't show up in the server startup logs. Chapter 4 --- estate/__init__.py | 1 + estate/__manifest__.py | 4 ++++ estate/security/ir.model.access.csv | 2 ++ 3 files changed, 7 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..899bcc97f0f 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ from . import models + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 37a0bac046f..7b842cf9d7f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,8 +7,12 @@ 'depends': [ 'base', ], + 'data': [ + 'security/ir.model.access.csv', + ], 'installable': True, 'application': True, 'author': 'Rini Pillai', + 'license': 'LGPL-3', } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..1b6677109b1 --- /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_user,access_estate_user,model_estate_properties,base.group_user,1,1,1,1 From 29b692e2c0db6fdaf4009a2ca98ea914af6187c1 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 3 Apr 2026 18:53:09 +0530 Subject: [PATCH 05/30] [CLN] estate: Code cleanup in real estate module Removed the trailing whitespaces and extra lines from multiple files. Chapter 4 --- estate/__init__.py | 1 - estate/__manifest__.py | 3 +-- estate/models/estate_properties.py | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 899bcc97f0f..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1 @@ from . import models - diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7b842cf9d7f..fb71b4ee895 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,5 +14,4 @@ 'application': True, 'author': 'Rini Pillai', 'license': 'LGPL-3', -} - +} \ No newline at end of file diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 5c33551308a..bfa2500cf55 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,9 +1,9 @@ from odoo import models, fields + class EstateProperties(models.Model): _name = "estate.properties" _description = "Real Estate Properties" - # _log_access = False name = fields.Char('Property Name', required=True, help="Name of the property shown") description = fields.Text('Description', help="Description of the property shown") @@ -17,5 +17,4 @@ class EstateProperties(models.Model): garage = fields.Boolean('Has Garage?', help="Does the proeprty have a garage?") garden = fields.Boolean('Has Garden?', help="Does the property have a garden?") garden_area = fields.Integer('Garden Area', help="Area of the garden of the property shown") - garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Directional orientation of the garden of the property shown") - + garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Directional orientation of the garden of the property shown") \ No newline at end of file From 390038b3fab4e2604e4d6f83abd9735b21544d24 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Mon, 6 Apr 2026 15:40:58 +0530 Subject: [PATCH 06/30] [ADD] estate: Added menu and an action in estate module Created an action for the model estate.properties as well as the estate_properties_view.xml in the views folder. Created three levels of menus for the action in the file estate_menus.xml. Defined both in proper sequential order in the estate manifest as well. Chapter 5 --- estate/__manifest__.py | 2 ++ estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_properties_view.xml | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_properties_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index fb71b4ee895..c00ab794a10 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,6 +9,8 @@ ], 'data': [ 'security/ir.model.access.csv', + 'views/estate_properties_view.xml', + 'views/estate_menus.xml', ], 'installable': True, 'application': True, diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..7ca3da2f739 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml new file mode 100644 index 00000000000..a722bbd8799 --- /dev/null +++ b/estate/views/estate_properties_view.xml @@ -0,0 +1,7 @@ + + + Properties + estate.properties + form,list + + \ No newline at end of file From 16130399d6f28638778ec7d42a54743534f74b76 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Mon, 6 Apr 2026 18:57:47 +0530 Subject: [PATCH 07/30] [ADD] estate: Availability Date Calculation in estate module Calculated the date with 3 month addition for the date_availability field dynamically. Chapter 5 --- estate/__manifest__.py | 2 +- estate/models/estate_properties.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index c00ab794a10..fb576b20b09 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -16,4 +16,4 @@ 'application': True, 'author': 'Rini Pillai', 'license': 'LGPL-3', -} \ No newline at end of file +} diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index bfa2500cf55..fde8347ab4c 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,5 +1,14 @@ +from logging import INFO from odoo import models, fields +import logging +from datetime import date, timedelta +from dateutil import relativedelta as rd +_logger = logging.getLogger(__name__) + +def get_availability_date(self): + # _logger.info("!!! %s", self) + return fields.Date.today() + rd.relativedelta(months=3) class EstateProperties(models.Model): _name = "estate.properties" @@ -8,13 +17,13 @@ class EstateProperties(models.Model): name = fields.Char('Property Name', required=True, help="Name of the property shown") description = fields.Text('Description', help="Description of the property shown") postcode = fields.Char('Postcode', help="Postal Code of the property shown") - date_availability = fields.Date('Availability Date', help="Date of availability of the property shown") + date_availability = fields.Date('Availability Date', help="Date of availability of the property shown", copy=False, default=get_availability_date) expected_price = fields.Float('Expected Price', required=True, help="Expected price of the property shown") - selling_price = fields.Float('Selling Price', help="Selling Price of the property shown") - bedrooms = fields.Integer('Bedrooms', help="Number of bedrooms in the property shown") + selling_price = fields.Float('Selling Price', help="Selling Price of the property shown", readonly=True, copy=False) + bedrooms = fields.Integer('Bedrooms', help="Number of bedrooms in the property shown", default=2) living_area = fields.Integer('Living Area', help="Number of living rooms in the property shown") facades = fields.Integer('Facades', help="Number of facades in the property shown") garage = fields.Boolean('Has Garage?', help="Does the proeprty have a garage?") garden = fields.Boolean('Has Garden?', help="Does the property have a garden?") garden_area = fields.Integer('Garden Area', help="Area of the garden of the property shown") - garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Directional orientation of the garden of the property shown") \ No newline at end of file + garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Directional orientation of the garden of the property shown") From 13bb5d4c3bedd112b67d65c07989ac119733944e Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Tue, 7 Apr 2026 18:45:40 +0530 Subject: [PATCH 08/30] [ADD] estate: Custom list view in estate module (partial) Created the custom view for estate module list view and code space formatting in estate_properties.py file. Chapter 6 --- estate/models/estate_properties.py | 16 +++++++++------- estate/views/estate_properties_view.xml | 12 +++++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index fde8347ab4c..fa9059da87e 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,23 +1,25 @@ -from logging import INFO from odoo import models, fields -import logging -from datetime import date, timedelta +# import logging +# from datetime import date, timedelta from dateutil import relativedelta as rd -_logger = logging.getLogger(__name__) -def get_availability_date(self): +# _logger = logging.getLogger(__name__) + + +def _get_availability_date(self): # _logger.info("!!! %s", self) return fields.Date.today() + rd.relativedelta(months=3) + class EstateProperties(models.Model): _name = "estate.properties" _description = "Real Estate Properties" - + name = fields.Char('Property Name', required=True, help="Name of the property shown") description = fields.Text('Description', help="Description of the property shown") postcode = fields.Char('Postcode', help="Postal Code of the property shown") - date_availability = fields.Date('Availability Date', help="Date of availability of the property shown", copy=False, default=get_availability_date) + date_availability = fields.Date('Availability Date', help="Date of availability of the property shown", copy=False, default=_get_availability_date) expected_price = fields.Float('Expected Price', required=True, help="Expected price of the property shown") selling_price = fields.Float('Selling Price', help="Selling Price of the property shown", readonly=True, copy=False) bedrooms = fields.Integer('Bedrooms', help="Number of bedrooms in the property shown", default=2) diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index a722bbd8799..2576024d7ca 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -1,7 +1,17 @@ + + estate.properties.list + estate.properties + + + + + + + Properties estate.properties - form,list + list \ No newline at end of file From 9b9b1a43967441d66a97e8762abdbad746af9aaa Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Wed, 8 Apr 2026 16:24:37 +0530 Subject: [PATCH 09/30] [ADD] estate: Custom list view in estate module Added all the required fields in the list view as specified in the task along with changed labels and width. Created user access to not have create permissions. Chapter 6 --- estate/security/ir.model.access.csv | 3 ++- estate/views/estate_properties_view.xml | 13 +++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 1b6677109b1..af2f02eb470 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_user,access_estate_user,model_estate_properties,base.group_user,1,1,1,1 +access_estate_user,access_estate_user,model_estate_properties,base.group_user,1,0,0,0 +access_estate_admin,access_estate_user,model_estate_properties,base.group_system,1,1,1,1 diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index 2576024d7ca..fda5d901a04 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -3,15 +3,20 @@ estate.properties.list estate.properties - - - + + + + + + + + Properties estate.properties - list + list,form \ No newline at end of file From 105b6e6780bb1422210f5bc698a281dcbb71747d Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Wed, 8 Apr 2026 18:56:29 +0530 Subject: [PATCH 10/30] [ADD] estate: Custom field for form view in estate module (partial) Created custom fields for form view in groups and notebook. Chapter 6 --- estate/models/estate_properties.py | 7 +++-- estate/views/estate_properties_view.xml | 39 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index fa9059da87e..6d8f73f2967 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,10 +1,10 @@ from odoo import models, fields -# import logging +import logging # from datetime import date, timedelta from dateutil import relativedelta as rd -# _logger = logging.getLogger(__name__) +_logger = logging.getLogger(__name__) def _get_availability_date(self): @@ -19,7 +19,8 @@ class EstateProperties(models.Model): name = fields.Char('Property Name', required=True, help="Name of the property shown") description = fields.Text('Description', help="Description of the property shown") postcode = fields.Char('Postcode', help="Postal Code of the property shown") - date_availability = fields.Date('Availability Date', help="Date of availability of the property shown", copy=False, default=_get_availability_date) + date_availability = fields.Date('Availability Date', help="Date of availability of the property shown", copy=False, default=lambda self: _logger.info("!!! date time calculated at creation") or fields.Date.add(fields.Date.context_today(self), months=3)) + _logger.info("Date set? %s", date_availability) expected_price = fields.Float('Expected Price', required=True, help="Expected price of the property shown") selling_price = fields.Float('Selling Price', help="Selling Price of the property shown", readonly=True, copy=False) bedrooms = fields.Integer('Bedrooms', help="Number of bedrooms in the property shown", default=2) diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index fda5d901a04..313d032922f 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -14,6 +14,45 @@ + + estate.properties.form + estate.properties + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
Properties estate.properties From be1dc7634a9fb2a782c841cc1566a028bd752b89 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Thu, 9 Apr 2026 18:18:32 +0530 Subject: [PATCH 11/30] [IMP] estate: Custom form view and search view for estate module Finished adding custom fields for the form view. Created a custom search view without filters using only fields. Addressed all the comments from the PR and fixed the code formatting issues. Chapter 5, 6 --- estate/models/estate_properties.py | 53 ++++++++++++++-------- estate/views/estate_menus.xml | 8 ++-- estate/views/estate_properties_view.xml | 60 +++++++++++++++++-------- 3 files changed, 81 insertions(+), 40 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 6d8f73f2967..52c7fb98383 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,7 +1,6 @@ -from odoo import models, fields import logging -# from datetime import date, timedelta from dateutil import relativedelta as rd +from odoo import fields, models _logger = logging.getLogger(__name__) @@ -13,20 +12,38 @@ def _get_availability_date(self): class EstateProperties(models.Model): - _name = "estate.properties" - _description = "Real Estate Properties" + _name = 'estate.properties' + _description = 'Real Estate Properties' - name = fields.Char('Property Name', required=True, help="Name of the property shown") - description = fields.Text('Description', help="Description of the property shown") - postcode = fields.Char('Postcode', help="Postal Code of the property shown") - date_availability = fields.Date('Availability Date', help="Date of availability of the property shown", copy=False, default=lambda self: _logger.info("!!! date time calculated at creation") or fields.Date.add(fields.Date.context_today(self), months=3)) - _logger.info("Date set? %s", date_availability) - expected_price = fields.Float('Expected Price', required=True, help="Expected price of the property shown") - selling_price = fields.Float('Selling Price', help="Selling Price of the property shown", readonly=True, copy=False) - bedrooms = fields.Integer('Bedrooms', help="Number of bedrooms in the property shown", default=2) - living_area = fields.Integer('Living Area', help="Number of living rooms in the property shown") - facades = fields.Integer('Facades', help="Number of facades in the property shown") - garage = fields.Boolean('Has Garage?', help="Does the proeprty have a garage?") - garden = fields.Boolean('Has Garden?', help="Does the property have a garden?") - garden_area = fields.Integer('Garden Area', help="Area of the garden of the property shown") - garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Directional orientation of the garden of the property shown") + name = fields.Char(string="Property Name", required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(string="Availability Date", copy=False, default=lambda self: fields.Date.add(fields.Date.context_today(self), months=3)) + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer(string="Living Area") + facades = fields.Integer() + garage = fields.Boolean(string="Has Garage?", help="Does the proeprty have a garage?") + garden = fields.Boolean(string="Has Garden?", help="Does the property have a garden?") + garden_area = fields.Integer(string="Garden Area") + garden_orientation = fields.Selection(selection= + [ + ('north', "North"), + ('south', "South"), + ('east', "East"), + ('west', "West") + ], + help="Directional orientation of the garden of the property shown" + ) + active = fields.Boolean(help="Should the property be listed?") + state = fields.Selection(string="Status", selection= + [ + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled") + ], + required=True, default='new', copy=False + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 7ca3da2f739..fa2bf81f039 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,7 @@ - - - + + + - \ No newline at end of file + diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index 313d032922f..c0749c02f9c 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -1,6 +1,6 @@ - - estate.properties.list + + estate.properties.view.list estate.properties @@ -14,38 +14,48 @@ - - estate.properties.form + + estate.properties.view.form estate.properties
+
+

+ +

+
- - - - - - - - - + + + + + + + + + + + + - - - + + + + + @@ -53,9 +63,23 @@
- + + estate.properties.view.search + estate.properties + + + + + + + + + + + + Properties estate.properties list,form -
\ No newline at end of file + From 6c70217933138c6f86fc58b204cddab7635569b5 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Thu, 9 Apr 2026 18:45:12 +0530 Subject: [PATCH 12/30] [CLN] estate: Code format cleanup in estate module Fixed whitespace issue in the file 'estate_properties.py' and removed unnecessary testing code from the custom views. Chapter 6 --- estate/models/estate_properties.py | 8 ++++---- estate/views/estate_properties_view.xml | 10 ---------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 52c7fb98383..1fb1fcb69b7 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -27,7 +27,7 @@ class EstateProperties(models.Model): garage = fields.Boolean(string="Has Garage?", help="Does the proeprty have a garage?") garden = fields.Boolean(string="Has Garden?", help="Does the property have a garden?") garden_area = fields.Integer(string="Garden Area") - garden_orientation = fields.Selection(selection= + garden_orientation = fields.Selection( [ ('north', "North"), ('south', "South"), @@ -37,13 +37,13 @@ class EstateProperties(models.Model): help="Directional orientation of the garden of the property shown" ) active = fields.Boolean(help="Should the property be listed?") - state = fields.Selection(string="Status", selection= - [ + state = fields.Selection( + [ ('new', "New"), ('offer_received', "Offer Received"), ('offer_accepted', "Offer Accepted"), ('sold', "Sold"), ('cancelled', "Cancelled") ], - required=True, default='new', copy=False + required=True, default='new', copy=False, string="Status" ) diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index c0749c02f9c..4c079cbddf2 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -35,16 +35,6 @@ - - - - - - - - - - From 0fb818e13ed2611b10ef8af3465520b73740140e Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 10 Apr 2026 18:24:59 +0530 Subject: [PATCH 13/30] [IMP] estate: Custom search view with filter and group by in estate module Creating custom filters in the search view using search domain and context, used logical filters for fields from the estate properties model. Chapter 6 --- estate/views/estate_properties_view.xml | 45 ++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index 4c079cbddf2..606137e1f0f 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -11,6 +11,14 @@ + + + + + + + +
@@ -34,6 +42,10 @@ + + + + @@ -60,10 +72,41 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c849d1cff303b4675049cabd30d1c814dee77ed0 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Mon, 13 Apr 2026 18:47:58 +0530 Subject: [PATCH 14/30] [ADD] estate: Created new property type model in estate module Created the new estate_property_type model in estate module with the name field to add property types to the listed properties, also created respective menu option called 'Settings' in the estate module. Rearranged the fields alphabetically for better readability. Performed the task to add default search filters on a view using context. Chapter 7 --- estate/models/estate_properties.py | 15 ++++++++------- estate/models/estate_property_type.py | 5 +++++ estate/views/estate_menus.xml | 5 ++++- estate/views/estate_properties_view.xml | 1 + estate/views/estate_property_type_view.xml | 7 +++++++ 5 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_type_view.xml diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 1fb1fcb69b7..4ccb7e9fe9b 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,5 +1,6 @@ import logging from dateutil import relativedelta as rd + from odoo import fields, models @@ -15,14 +16,11 @@ class EstateProperties(models.Model): _name = 'estate.properties' _description = 'Real Estate Properties' - name = fields.Char(string="Property Name", required=True) - description = fields.Text() - postcode = fields.Char() + active = fields.Boolean(help="Should the property be listed?") + bedrooms = fields.Integer(default=2) date_availability = fields.Date(string="Availability Date", copy=False, default=lambda self: fields.Date.add(fields.Date.context_today(self), months=3)) + description = fields.Text() expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) - bedrooms = fields.Integer(default=2) - living_area = fields.Integer(string="Living Area") facades = fields.Integer() garage = fields.Boolean(string="Has Garage?", help="Does the proeprty have a garage?") garden = fields.Boolean(string="Has Garden?", help="Does the property have a garden?") @@ -36,7 +34,10 @@ class EstateProperties(models.Model): ], help="Directional orientation of the garden of the property shown" ) - active = fields.Boolean(help="Should the property be listed?") + living_area = fields.Integer(string="Living Area") + name = fields.Char(string="Property Name", required=True) + postcode = fields.Char() + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) state = fields.Selection( [ ('new', "New"), diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..824b18855a2 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,5 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + name = fields.Char(required=True) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index fa2bf81f039..2de1b885aa1 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,10 @@ - + + + + diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index 606137e1f0f..e59192c8556 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -114,5 +114,6 @@ Properties estate.properties list,form + {'search_default_available': 1, 'search_default_flexible': 1} diff --git a/estate/views/estate_property_type_view.xml b/estate/views/estate_property_type_view.xml new file mode 100644 index 00000000000..197d4da81ff --- /dev/null +++ b/estate/views/estate_property_type_view.xml @@ -0,0 +1,7 @@ + + + Property Type + estate.properties + form + + From af9d5543a73786b270a80c5cdf94dedbd0cacfeb Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Mon, 13 Apr 2026 18:58:59 +0530 Subject: [PATCH 15/30] [FIX] estate: Missing view in manifest in estate module Added the missing view in the manifest for the new estate_property_type view Chapter 7 --- estate/__manifest__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index fb576b20b09..549272d83c7 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_properties_view.xml', + 'views/estate_property_type_view.xml', 'views/estate_menus.xml', ], 'installable': True, From 82c64694643123752e07e9e96eb1f81665b6c3ab Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Tue, 14 Apr 2026 18:40:05 +0530 Subject: [PATCH 16/30] [ADD] estate: New Property Type and Property Tag models in real estate module Finished adding new action to property type with default views, added buyer and salesperson fields using Many2one. Created new model for property tags, added it in the menu, created its action and default view. Used Many2many field for tag in estate_properties model. Chapter 7 --- estate/__manifest__.py | 1 + estate/models/__init__.py | 2 ++ estate/models/estate_properties.py | 12 ++++++++++++ estate/models/estate_property_tag.py | 8 ++++++++ estate/models/estate_property_type.py | 13 ++++++++++++- estate/security/ir.model.access.csv | 6 +++++- estate/views/estate_menus.xml | 3 ++- estate/views/estate_properties_view.xml | 16 ++++++++++++++++ estate/views/estate_property_tag_view.xml | 7 +++++++ estate/views/estate_property_type_view.xml | 4 ++-- 10 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/views/estate_property_tag_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 549272d83c7..598a6881787 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,6 +11,7 @@ 'security/ir.model.access.csv', 'views/estate_properties_view.xml', 'views/estate_property_type_view.xml', + 'views/estate_property_tag_view.xml', 'views/estate_menus.xml', ], 'installable': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c74fc2fd8eb..183d65d5257 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,3 @@ from . import estate_properties +from . import estate_property_type +from . import estate_property_tag diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 4ccb7e9fe9b..e58cbda8bd5 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -11,6 +11,12 @@ def _get_availability_date(self): # _logger.info("!!! %s", self) return fields.Date.today() + rd.relativedelta(months=3) +def _get_salesperson(self): + # _logger.info(self.env.user.name) + # _logger.info(self.env.user.id) + # _logger.info(self.env.user) + return self.env.user + class EstateProperties(models.Model): _name = 'estate.properties' @@ -18,6 +24,7 @@ class EstateProperties(models.Model): active = fields.Boolean(help="Should the property be listed?") bedrooms = fields.Integer(default=2) + buyer_id = fields.Many2one(comodel_name='res.partner', copy=False) date_availability = fields.Date(string="Availability Date", copy=False, default=lambda self: fields.Date.add(fields.Date.context_today(self), months=3)) description = fields.Text() expected_price = fields.Float(string="Expected Price", required=True) @@ -37,6 +44,10 @@ class EstateProperties(models.Model): living_area = fields.Integer(string="Living Area") name = fields.Char(string="Property Name", required=True) postcode = fields.Char() + property_type_colour = fields.Selection(string="Type Colour", related="property_type_id.colour", readonly=False) + property_type_id = fields.Many2one(string="Property Type", comodel_name="estate.property.type") + salesperson_id = fields.Many2one(comodel_name='res.partner', default=lambda self: self.env.user.partner_id) + # salesperson = fields.Char(default=_get_salesperson) selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) state = fields.Selection( [ @@ -48,3 +59,4 @@ class EstateProperties(models.Model): ], required=True, default='new', copy=False, string="Status" ) + tag_ids = fields.Many2many(comodel_name='estate.property.tag') diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..b9b6aeec739 --- /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 = 'Estate property tags' + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 824b18855a2..56e1eb49d26 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -2,4 +2,15 @@ class EstatePropertyType(models.Model): - name = fields.Char(required=True) + _name = 'estate.property.type' + _description = 'Estate Property Types' + _rec_name = 'type' + + type = fields.Char(required=True) + colour = fields.Selection( + [ + ('red', 'Red'), + ('green', 'Green'), + ('yellow', 'Yellow') + ] + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index af2f02eb470..ca67257eebb 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,3 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_user,access_estate_user,model_estate_properties,base.group_user,1,0,0,0 -access_estate_admin,access_estate_user,model_estate_properties,base.group_system,1,1,1,1 +access_estate_admin,access_estate_admin,model_estate_properties,base.group_system,1,1,1,1 +access_estate_property_type_user,access_estate_property_type_user,model_estate_property_type,base.group_user,1,0,0,0 +access_estate_property_type_admin,access_estate_property_type_admin,model_estate_property_type,base.group_system,1,1,1,1 +access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_system,1,0,0,0 +access_estate_property_tag_admin,access_estate_property_tag_admin,model_estate_property_tag,base.group_system,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 2de1b885aa1..a9fc627daa7 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,7 +4,8 @@ - + + diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index e59192c8556..b5da249c279 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -22,6 +22,7 @@ + estate.properties.view.form estate.properties @@ -32,8 +33,15 @@

+ + + + + + + @@ -60,11 +68,18 @@ + + + + + +
+ estate.properties.view.search estate.properties @@ -110,6 +125,7 @@ + Properties estate.properties diff --git a/estate/views/estate_property_tag_view.xml b/estate/views/estate_property_tag_view.xml new file mode 100644 index 00000000000..96f301bcb60 --- /dev/null +++ b/estate/views/estate_property_tag_view.xml @@ -0,0 +1,7 @@ + + + Property Tag + estate.property.tag + list,form + + diff --git a/estate/views/estate_property_type_view.xml b/estate/views/estate_property_type_view.xml index 197d4da81ff..0a5cd0f8035 100644 --- a/estate/views/estate_property_type_view.xml +++ b/estate/views/estate_property_type_view.xml @@ -1,7 +1,7 @@ Property Type - estate.properties - form + estate.property.type + list,form From 8d18d21cbe1e97bbf743c6529f2797c98a239f4d Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Tue, 14 Apr 2026 18:58:12 +0530 Subject: [PATCH 17/30] [CLN] estate: Code Cleanup in estate_properties.py Added missing spaces to ensure proper styling has been followed throughout Chapter 7 --- estate/models/estate_properties.py | 1 + 1 file changed, 1 insertion(+) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index e58cbda8bd5..a06ac2e863b 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -11,6 +11,7 @@ def _get_availability_date(self): # _logger.info("!!! %s", self) return fields.Date.today() + rd.relativedelta(months=3) + def _get_salesperson(self): # _logger.info(self.env.user.name) # _logger.info(self.env.user.id) From e7ff64ca5887d9b3d7eaf2a2b979852e08d4b652 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Wed, 15 Apr 2026 11:57:26 +0530 Subject: [PATCH 18/30] [ADD] estate: New Property Offer model in estate module Created the estate propert offer model with the required fields also, created a list view for it and defined access rights. Added offer_ids to the estate properties model using one2many. Chapter 7 --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_properties.py | 1 + estate/models/estate_property_offer.py | 17 +++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 1 + estate/views/estate_properties_view.xml | 3 +++ estate/views/estate_property_offer_view.xml | 18 ++++++++++++++++++ 8 files changed, 44 insertions(+) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/views/estate_property_offer_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 598a6881787..3a7c6d9b6a5 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,7 @@ 'views/estate_properties_view.xml', 'views/estate_property_type_view.xml', 'views/estate_property_tag_view.xml', + 'views/estate_property_offer_view.xml', 'views/estate_menus.xml', ], 'installable': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 183d65d5257..9a474ce9837 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,3 +1,4 @@ from . import estate_properties from . import estate_property_type from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index a06ac2e863b..aa9361a59bc 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -44,6 +44,7 @@ class EstateProperties(models.Model): ) living_area = fields.Integer(string="Living Area") name = fields.Char(string="Property Name", required=True) + offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') postcode = fields.Char() property_type_colour = fields.Selection(string="Type Colour", related="property_type_id.colour", readonly=False) property_type_id = fields.Many2one(string="Property Type", comodel_name="estate.property.type") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..118129ac828 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Real Estate Property Offers' + + partner_id = fields.Many2one(comodel_name='res.partner', required=True) + price = fields.Float() + property_id = fields.Many2one(comodel_name='estate.properties', required=True, readonly=True) + status = fields.Selection( + [ + ('refused', "Refused"), + ('accepted', "Accepted") + ], + copy=False + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index ca67257eebb..d0363679cc3 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -5,3 +5,5 @@ access_estate_property_type_user,access_estate_property_type_user,model_estate_p access_estate_property_type_admin,access_estate_property_type_admin,model_estate_property_type,base.group_system,1,1,1,1 access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_system,1,0,0,0 access_estate_property_tag_admin,access_estate_property_tag_admin,model_estate_property_tag,base.group_system,1,1,1,1 +access_estate_property_offer_user,access_estate_property_offer_user,model_estate_property_offer,base.group_user,1,0,0,0 +access_estate_property_offer_admin,access_estate_property_offer_admin,model_estate_property_offer,base.group_system,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index a9fc627daa7..797048afa1d 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -6,6 +6,7 @@ + diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index b5da249c279..d5abf08f02a 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -68,6 +68,9 @@ + + + diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml new file mode 100644 index 00000000000..32ec75dfffa --- /dev/null +++ b/estate/views/estate_property_offer_view.xml @@ -0,0 +1,18 @@ + + + estate.property.offer.view.list + estate.property.offer + + + + + + + + + + + From 69fac9fb23db079a3b625ec963c8562bbd609bc6 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Wed, 15 Apr 2026 18:49:46 +0530 Subject: [PATCH 19/30] [IMP] estate: Computed fields in estate module Created computed fields for best price, total area in estate_properties model and added them in their respective views. Created computed fields for validity in estate_propert_offer model and added them in their respective views. Chapter 8 --- estate/models/estate_properties.py | 29 +++++++++++--- estate/models/estate_property_offer.py | 25 +++++++++++- estate/views/estate_properties_view.xml | 43 ++++++++++++--------- estate/views/estate_property_offer_view.xml | 28 +++++++++++++- 4 files changed, 96 insertions(+), 29 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index aa9361a59bc..af96b4d5bf7 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -1,7 +1,7 @@ import logging from dateutil import relativedelta as rd -from odoo import fields, models +from odoo import fields, models, api _logger = logging.getLogger(__name__) @@ -25,6 +25,7 @@ class EstateProperties(models.Model): active = fields.Boolean(help="Should the property be listed?") bedrooms = fields.Integer(default=2) + best_price = fields.Integer(compute="_compute_best_price") buyer_id = fields.Many2one(comodel_name='res.partner', copy=False) date_availability = fields.Date(string="Availability Date", copy=False, default=lambda self: fields.Date.add(fields.Date.context_today(self), months=3)) description = fields.Text() @@ -32,7 +33,7 @@ class EstateProperties(models.Model): facades = fields.Integer() garage = fields.Boolean(string="Has Garage?", help="Does the proeprty have a garage?") garden = fields.Boolean(string="Has Garden?", help="Does the property have a garden?") - garden_area = fields.Integer(string="Garden Area") + garden_area = fields.Integer() garden_orientation = fields.Selection( [ ('north', "North"), @@ -42,15 +43,15 @@ class EstateProperties(models.Model): ], help="Directional orientation of the garden of the property shown" ) - living_area = fields.Integer(string="Living Area") + living_area = fields.Integer() name = fields.Char(string="Property Name", required=True) offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') postcode = fields.Char() - property_type_colour = fields.Selection(string="Type Colour", related="property_type_id.colour", readonly=False) - property_type_id = fields.Many2one(string="Property Type", comodel_name="estate.property.type") + property_type_colour = fields.Selection(related="property_type_id.colour", readonly=False) + property_type_id = fields.Many2one(comodel_name="estate.property.type") salesperson_id = fields.Many2one(comodel_name='res.partner', default=lambda self: self.env.user.partner_id) # salesperson = fields.Char(default=_get_salesperson) - selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + selling_price = fields.Float(readonly=True, copy=False) state = fields.Selection( [ ('new', "New"), @@ -62,3 +63,19 @@ class EstateProperties(models.Model): required=True, default='new', copy=False, string="Status" ) tag_ids = fields.Many2many(comodel_name='estate.property.tag') + total_area = fields.Integer(compute="_compute_total_area") + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + _logger.error(self) + for property in self: + # _logger.error(property._fields) + # _logger.error(property) + property.total_area = property.living_area + property.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for property in self: + # _logger.error(self.mapped('offer_ids.price')) + offer_prices = self.mapped('offer_ids.price') + property.best_price = max(offer_prices) if offer_prices else 0 diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 118129ac828..cd7921897c6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,13 +1,20 @@ -from odoo import fields, models +import datetime +import logging + +from odoo import api, fields, models + + +_logger = logging.getLogger(__name__) class EstatePropertyOffer(models.Model): _name = 'estate.property.offer' _description = 'Real Estate Property Offers' + deadline = fields.Date() partner_id = fields.Many2one(comodel_name='res.partner', required=True) price = fields.Float() - property_id = fields.Many2one(comodel_name='estate.properties', required=True, readonly=True) + property_id = fields.Many2one(comodel_name='estate.properties', readonly=True) status = fields.Selection( [ ('refused', "Refused"), @@ -15,3 +22,17 @@ class EstatePropertyOffer(models.Model): ], copy=False ) + validity = fields.Integer(compute="_compute_validity", inverse="_inverse_deadline") + + @api.depends('deadline') + def _compute_validity(self): + for offer in self: + offer.validity = (offer.deadline - offer.create_date.date()).days if offer.deadline and offer.create_date else 0 + + @api.depends('validity') + def _inverse_deadline(self): + for offer in self: + # _logger.error(fields.Date.context_today(offer) + datetime.timedelta(days=offer.validity)) + # _logger.error(offer._fields) + start_date = offer.create_date.date() if offer.create_date else fields.Date.context_today(offer) + offer.deadline = start_date + datetime.timedelta(days=offer.validity) diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index d5abf08f02a..099b3b30037 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -11,6 +11,7 @@ + @@ -33,28 +34,31 @@

- - - - - - - - - - - - - - - - + +
- - + + + + + + + + + + + + + + + + + + + - +
@@ -66,6 +70,7 @@ + diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml index 32ec75dfffa..62226079f52 100644 --- a/estate/views/estate_property_offer_view.xml +++ b/estate/views/estate_property_offer_view.xml @@ -6,13 +6,37 @@ + + +
- + + + + + + + + + + Property Offer estate.property.offer - --> + From 9172235afab88dfe0856535474cc0a1941b2733a Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Thu, 16 Apr 2026 12:54:02 +0530 Subject: [PATCH 20/30] [IMP] estate: Method decorators in estate module Added validation checks using constraints for date and prices in estate_properties model and estate_property_offer model Chapter 8 --- estate/models/estate_properties.py | 25 ++++++++++++++++++++++++- estate/models/estate_property_offer.py | 13 +++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index af96b4d5bf7..75ba3104b07 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -2,6 +2,7 @@ from dateutil import relativedelta as rd from odoo import fields, models, api +from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -67,7 +68,7 @@ class EstateProperties(models.Model): @api.depends('living_area', 'garden_area') def _compute_total_area(self): - _logger.error(self) + # _logger.error(self) for property in self: # _logger.error(property._fields) # _logger.error(property) @@ -79,3 +80,25 @@ def _compute_best_price(self): # _logger.error(self.mapped('offer_ids.price')) offer_prices = self.mapped('offer_ids.price') property.best_price = max(offer_prices) if offer_prices else 0 + + @api.constrains('expected_price') + def _check_expected_price(self): + for property in self: + if property.expected_price < 0: + raise ValidationError("Value cannot be less than zero.") + + @api.onchange('garden') + def _onchange_garden(self): + # _logger.error(self.garden) + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = None + + @api.constrains('date_availability') + def _check_date_availability(self): + for property in self: + if property.date_availability < fields.Date.context_today(property): + raise ValidationError("Past dates not allowed") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index cd7921897c6..a30114ff15d 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -2,6 +2,7 @@ import logging from odoo import api, fields, models +from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -36,3 +37,15 @@ def _inverse_deadline(self): # _logger.error(offer._fields) start_date = offer.create_date.date() if offer.create_date else fields.Date.context_today(offer) offer.deadline = start_date + datetime.timedelta(days=offer.validity) + + @api.constrains('price') + def _check_price(self): + for property in self: + if property.price < 0: + raise ValidationError("Value cannot be less than zero.") + + @api.constrains('deadline') + def _check_deadline(self): + for property in self: + if property.deadline < fields.Date.context_today(property): + raise ValidationError("Past dates not allowed") From 076b2df138a10e2e4a4d704252fdb26dd387f139 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 17 Apr 2026 14:59:30 +0530 Subject: [PATCH 21/30] [IMP] estate: Button actions in estate module Marked properties in the estate_properties model as sold/cancelled using buttons and updated the view to reflect the states. Created accept/refuse button for offers listed in the estate_property_offer model, also showing price and buyer in the property form of the accepted offer and updated its respective view. Chapter 9 --- estate/models/estate_properties.py | 57 +++++++++++++++++---- estate/models/estate_property_offer.py | 41 ++++++++++++--- estate/models/estate_property_type.py | 2 +- estate/views/estate_properties_view.xml | 18 +++++-- estate/views/estate_property_offer_view.xml | 16 +++--- 5 files changed, 104 insertions(+), 30 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 75ba3104b07..a23acfaee7a 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -2,7 +2,7 @@ from dateutil import relativedelta as rd from odoo import fields, models, api -from odoo.exceptions import ValidationError +from odoo.exceptions import UserError _logger = logging.getLogger(__name__) @@ -24,7 +24,7 @@ class EstateProperties(models.Model): _name = 'estate.properties' _description = 'Real Estate Properties' - active = fields.Boolean(help="Should the property be listed?") + active = fields.Boolean(help="Should the property be listed?", default=True) bedrooms = fields.Integer(default=2) best_price = fields.Integer(compute="_compute_best_price") buyer_id = fields.Many2one(comodel_name='res.partner', copy=False) @@ -48,6 +48,7 @@ class EstateProperties(models.Model): name = fields.Char(string="Property Name", required=True) offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') postcode = fields.Char() + price_gap= fields.Float(compute="_compute_price_gap") property_type_colour = fields.Selection(related="property_type_id.colour", readonly=False) property_type_id = fields.Many2one(comodel_name="estate.property.type") salesperson_id = fields.Many2one(comodel_name='res.partner', default=lambda self: self.env.user.partner_id) @@ -78,15 +79,15 @@ def _compute_total_area(self): def _compute_best_price(self): for property in self: # _logger.error(self.mapped('offer_ids.price')) - offer_prices = self.mapped('offer_ids.price') + offer_prices = property.mapped('offer_ids.price') property.best_price = max(offer_prices) if offer_prices else 0 - + @api.constrains('expected_price') def _check_expected_price(self): for property in self: if property.expected_price < 0: - raise ValidationError("Value cannot be less than zero.") - + raise UserError("Value cannot be less than zero.") + @api.onchange('garden') def _onchange_garden(self): # _logger.error(self.garden) @@ -97,8 +98,44 @@ def _onchange_garden(self): self.garden_area = 0 self.garden_orientation = None - @api.constrains('date_availability') - def _check_date_availability(self): + # @api.constrains('date_availability') + # def _check_date_availability(self): + # for property in self: + # if property.date_availability < fields.Date.context_today(property): + # raise ValidationError("Past dates not allowed") + + @api.depends('best_price', 'expected_price') + def _compute_price_gap(self): + for property in self: + if (property.best_price and property.expected_price) != 0: + property.price_gap= property.best_price - property.expected_price + else: + property.price_gap= 0 + + @api.onchange('state') + def _onchnage_state(self): + if self.state == 'cancelled': + self.active = False + else: + self.active = True + + def property_cancelled(self): + for property in self: + if property.state == 'cancelled': + raise UserError("Property already cancelled") + elif property.state != 'sold': + property.state = 'cancelled' + property.active = False + else: + raise UserError("A sold property cannot be cancelled") + return True + + def property_sold(self): for property in self: - if property.date_availability < fields.Date.context_today(property): - raise ValidationError("Past dates not allowed") + if property.state == 'sold': + raise UserError("Property already sold") + elif property.state != 'cancelled': + property.state = 'sold' + else: + raise UserError("A cancelled property cannot be sold") + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index a30114ff15d..912d41e22d6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -2,7 +2,7 @@ import logging from odoo import api, fields, models -from odoo.exceptions import ValidationError +from odoo.exceptions import ValidationError, UserError _logger = logging.getLogger(__name__) @@ -43,9 +43,36 @@ def _check_price(self): for property in self: if property.price < 0: raise ValidationError("Value cannot be less than zero.") - - @api.constrains('deadline') - def _check_deadline(self): - for property in self: - if property.deadline < fields.Date.context_today(property): - raise ValidationError("Past dates not allowed") + + # @api.constrains('deadline') + # def _check_deadline(self): + # for property in self: + # if property.deadline < fields.Date.context_today(property): + # raise ValidationError("Past dates not allowed") + + def _refuse_remaining_offers(self, offer_id, all_offers): + # _logger.error(all_offers) + for offer in all_offers: + if offer.id != offer_id: + offer.status = 'refused' + + def offer_accepted(self): + offer_id = 0 + for offer in self: + if offer.status == 'accepted': + raise UserError("Property already accepted!") + offer.status = 'accepted' + all_offers = offer.property_id.offer_ids + # _logger.error(all_offers) + offer_id = offer.id + offer._refuse_remaining_offers(offer_id, all_offers) + offer.property_id.buyer_id = offer.partner_id + offer.property_id.selling_price = offer.price + return True + + def offer_refused(self): + for offer in self: + if offer.status == 'refused': + raise UserError("Property already refused!") + offer.status = 'refused' + return True diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 56e1eb49d26..8711833d0ed 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,7 +6,6 @@ class EstatePropertyType(models.Model): _description = 'Estate Property Types' _rec_name = 'type' - type = fields.Char(required=True) colour = fields.Selection( [ ('red', 'Red'), @@ -14,3 +13,4 @@ class EstatePropertyType(models.Model): ('yellow', 'Yellow') ] ) + type = fields.Char(required=True) diff --git a/estate/views/estate_properties_view.xml b/estate/views/estate_properties_view.xml index 099b3b30037..5f15336a947 100644 --- a/estate/views/estate_properties_view.xml +++ b/estate/views/estate_properties_view.xml @@ -8,10 +8,12 @@ - + - + + + @@ -29,13 +31,18 @@ estate.properties
+
+

- + +
@@ -44,10 +51,10 @@ - + - + @@ -56,6 +63,7 @@ +
diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml index 62226079f52..a0b09086af9 100644 --- a/estate/views/estate_property_offer_view.xml +++ b/estate/views/estate_property_offer_view.xml @@ -7,9 +7,11 @@ - + +

From 48c7de90c2574749090dae2f83e155aa62a2620a Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Thu, 30 Apr 2026 12:10:38 +0530 Subject: [PATCH 28/30] [IMP] estate: Stat Button in estate module finished Fixed the stat button to add the offers smart button by adding the domain for the view to only show offers related to that type Chapter 11 --- estate/models/estate_property_type.py | 2 ++ estate/views/estate_property_offer_view.xml | 2 +- estate/views/estate_property_type_view.xml | 9 +++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index f0ac8b5dea3..3fbf4fc91c0 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -38,6 +38,8 @@ def _compute_offer_count(self): property_type.offer_count = len(property_type.offer_ids) def action_see_offers(self): + # breakpoint() self.ensure_one() action = self.env['ir.actions.actions']._for_xml_id('estate.estate_property_offer_action') + action['domain'] = [('property_type_id', '=', self.id)] return action diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml index a15f607a879..7a5e8e83b3b 100644 --- a/estate/views/estate_property_offer_view.xml +++ b/estate/views/estate_property_offer_view.xml @@ -39,7 +39,7 @@ - Property Offer + Offers estate.property.offer list,form diff --git a/estate/views/estate_property_type_view.xml b/estate/views/estate_property_type_view.xml index 122ab061cbb..315ab9a4b61 100644 --- a/estate/views/estate_property_type_view.xml +++ b/estate/views/estate_property_type_view.xml @@ -5,15 +5,16 @@
-
+
+ +

- From ade3985a088f2d14800e9a067415e951d784d255 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Thu, 30 Apr 2026 18:55:12 +0530 Subject: [PATCH 29/30] [IMP] estate: Experimenting with stat buttons in estate module Created stat buttons for different use cases to practice how to load views and to understand compute and actions better. Made stat buttons to view high price properties in a type, and offers with close deadlines for a type. Chapter 11 --- estate/models/estate_properties.py | 36 +++++++++++------ estate/models/estate_property_offer.py | 40 +++++++++++++------ estate/models/estate_property_type.py | 44 ++++++++++++++++++++- estate/views/estate_property_offer_view.xml | 5 +-- estate/views/estate_property_type_view.xml | 14 ++++++- 5 files changed, 111 insertions(+), 28 deletions(-) diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index 141c776afb7..d2a76e2f5fe 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -78,13 +78,13 @@ class EstateProperties(models.Model): @api.constrains('selling_price') def _check_selling_price(self): # breakpoint() - _logger.error(self.id) + # _logger.error(self.id) for property in self: sp_threshold = 0.9 * property.expected_price - _logger.error(sp_threshold) - _logger.error(property.selling_price) - _logger.error(property.expected_price) - _logger.error(property.id) + # _logger.error(sp_threshold) + # _logger.error(property.selling_price) + # _logger.error(property.expected_price) + # _logger.error(property.id) if property.selling_price < sp_threshold: raise ValidationError("Selling price cannot be less than 90% of the expected price") @@ -173,23 +173,37 @@ def _compute_commission(self): property.commission = property.selling_price * 0.06 def property_accept(self): + breakpoint() # best_offers = [] # for property in self.offer_ids: # best_offers.append(property.price) # _logger.error(best_offers) # property_to_accept = max(best_offers) # _logger.error(property_to_accept) - if self.offer_ids: - best = self.offer_ids.search([('price', '=', self.best_price)]) - # _logger.error(best) - best.offer_accepted() - else: - raise ValidationError("No offers listed for this property") + # if self.offer_ids: + # best = self.offer_ids.search([('price', '=', self.best_price), ('status', 'not in', ['refused'])]) + # if best: + # best.offer_accepted() + # return + # best = self.offer_ids.search([('price', '<', self.best_price), ('status', 'not in', ['refused'])], 0, 1, order='price DESC') + # best.ensure_one() + # best.offer_accepted() + # # _logger.error(best) + # else: + # raise ValidationError("No offers listed for this property") # for property in self.offer_ids: # if property.status == 'accepted': # pass # else: # property.status = 'refused' + self.ensure_one() + best_offer = self.offer_ids.filtered(lambda offer: offer.status not in ['accepted', 'refused']) + + if not best_offer: + raise ValidationError("No offers listed for this property") + + best_offer = best_offer.sorted(lambda offer: offer.price, reverse=True)[0] + return best_offer.offer_accepted() @api.onchange('offer_ids') def offer_received_state(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index db2043befce..5501487bbc3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -3,6 +3,7 @@ from odoo import api, fields, models from odoo.exceptions import UserError +from odoo.orm.utils import ValidationError _logger = logging.getLogger(__name__) @@ -65,19 +66,34 @@ def _refuse_remaining_offers(self, offer_id, all_offers): offer.status = 'refused' def offer_accepted(self): - offer_id = 0 + # offer_id = 0 # breakpoint() - for offer in self: - if offer.status == 'accepted' and offer.property_id.state == 'offer_accepted': - raise UserError("Property already accepted!") - offer.status = 'accepted' - all_offers = offer.property_id.offer_ids - # _logger.error(all_offers) - offer_id = offer.id - offer._refuse_remaining_offers(offer_id, all_offers) - offer.property_id.buyer_id = offer.partner_id - offer.property_id.selling_price = offer.price - offer.property_id.state = 'offer_accepted' + # for offer in self: + # if offer.status == 'accepted' and offer.property_id.state == 'offer_accepted': + # raise UserError("Property already accepted!") + # offer.status = 'accepted' + # all_offers = offer.property_id.offer_ids + # # _logger.error(all_offers) + # offer_id = offer.id + # offer._refuse_remaining_offers(offer_id, all_offers) + # offer.property_id.buyer_id = offer.partner_id + # offer.property_id.selling_price = offer.price + # offer.property_id.state = 'offer_accepted' + # return True + self.ensure_one() + property = self.property_id + if property.state == 'offer_accepted' and self.status == 'accepted': + raise ValidationError("Property already accepted!") + remaining_offers = property.offer_ids - self + remaining_offers.write({ + 'status': 'refused' + }) + self.status = 'accepted' + property.write({ + 'buyer_id': self.partner_id, + 'selling_price': self.price, + 'state': 'offer_accepted', + }) return True def offer_refused(self): diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 3fbf4fc91c0..02657d7afc4 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,5 @@ import logging +from datetime import timedelta from odoo import api, fields, models @@ -19,8 +20,10 @@ class EstatePropertyType(models.Model): ('yellow', 'Yellow') ] ) + offer_alert = fields.Integer(compute='_compute_offer_alert') offer_count = fields.Integer(compute='_compute_offer_count') offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_type_id') + pricey_count = fields.Integer(compute='_compute_pricey_count') property_ids = fields.One2many(comodel_name='estate.properties', inverse_name='property_type_id') type = fields.Char(required=True) @@ -37,9 +40,48 @@ def _compute_offer_count(self): # _logger.error(property_type.property_ids.offer_ids) property_type.offer_count = len(property_type.offer_ids) + @api.depends('offer_ids.deadline') + def _compute_offer_alert(self): + # breakpoint() + for property in self: + today = fields.Date.context_today(property) + alert = timedelta(days=1) + property.offer_alert = len(property.offer_ids.filtered( + lambda o: o.deadline and abs(o.deadline - today) <= alert + and o.status not in ['refused', 'accepted']) + ) + # _logger.error(property.offer_alert) + + @api.depends('property_ids.expected_price') + def _compute_pricey_count(self): + for property in self: + property.pricey_count = len(property.property_ids.filtered(lambda prop: prop.expected_price > 100000)) + def action_see_offers(self): # breakpoint() self.ensure_one() action = self.env['ir.actions.actions']._for_xml_id('estate.estate_property_offer_action') action['domain'] = [('property_type_id', '=', self.id)] - return action + return action + + def action_pricey_property(self): + # breakpoint() + self.ensure_one() + action = self.env['ir.actions.actions']._for_xml_id('estate.estate_properties_action') + action['domain'] = [ + ('expected_price', '>=', 100000), + ('property_type_id', '=', self.id) + ] + action['context'] = {'search_default_available': 0} + return action + + def action_offer_alert(self): + # breakpoint() + self.ensure_one() + action = self.env['ir.actions.actions']._for_xml_id('estate.estate_property_offer_action') + action['domain'] = [ + ('property_type_id', '=', self.id), + ('deadline', '>=', fields.Date.context_today(self)), + ('status', 'not in', ['refused', 'accepted']) + ] + return action diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml index 7a5e8e83b3b..047d39825ce 100644 --- a/estate/views/estate_property_offer_view.xml +++ b/estate/views/estate_property_offer_view.xml @@ -6,11 +6,10 @@ - + + +

From 8068552d0469a4544c0821f82075003a51d9c7a3 Mon Sep 17 00:00:00 2001 From: ripil-odoo Date: Fri, 1 May 2026 10:31:36 +0530 Subject: [PATCH 30/30] [FIX] estate: Action not found error in estate module Rearranged order of the views being loaded to fix the offer action not found error and also removed the breakpoints from code to comply with styling rules. Chapter 11 --- estate/__manifest__.py | 2 +- estate/models/estate_properties.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3a7c6d9b6a5..4d44accd006 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,9 +10,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_properties_view.xml', + 'views/estate_property_offer_view.xml', 'views/estate_property_type_view.xml', 'views/estate_property_tag_view.xml', - 'views/estate_property_offer_view.xml', 'views/estate_menus.xml', ], 'installable': True, diff --git a/estate/models/estate_properties.py b/estate/models/estate_properties.py index d2a76e2f5fe..f20a4d52e87 100644 --- a/estate/models/estate_properties.py +++ b/estate/models/estate_properties.py @@ -173,7 +173,7 @@ def _compute_commission(self): property.commission = property.selling_price * 0.06 def property_accept(self): - breakpoint() + # breakpoint() # best_offers = [] # for property in self.offer_ids: # best_offers.append(property.price) @@ -201,7 +201,7 @@ def property_accept(self): if not best_offer: raise ValidationError("No offers listed for this property") - + best_offer = best_offer.sorted(lambda offer: offer.price, reverse=True)[0] return best_offer.offer_accepted()