From 2e0af734f802d8bbbcd2a0c86821ddf7f77b61ae Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Thu, 30 Oct 2025 17:01:50 +0530 Subject: [PATCH 01/19] [ADD] estate: new 'estate' module Initializes the basic structure for the real estate module. - Creates the module scaffolding (manifest, init, and base directories). - Defines the initial 'estate.property' model. - Adds base fields (columns) to the new model. --- estate/__init__.py | 1 + estate/__manifest__.py | 6 ++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 25 +++++++++++++++++++++++++ 4 files changed, 33 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..a700d35d54a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,6 @@ +{ + 'name': 'Real Estate', + 'depends': ['base'], + 'license': 'AGPL-3', + 'author': 'Odoo S.A.' +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..348849f0867 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,25 @@ +from odoo import models, fields + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(required=True) + description = fields.Text() + pincode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer(required=True) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer(string="Garden Area (sqft)") + garden_orientation = fields.Selection([ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ]) \ No newline at end of file From ca8c609719904cab19c38ff3105b25a1198d7fd9 Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Thu, 30 Oct 2025 17:01:41 +0530 Subject: [PATCH 02/19] [ADD] estate: new 'estate' module Initializes the basic structure for the real estate module. - Creates the module scaffolding (manifest, init, and base directories). - Defines the initial 'estate.property' model. - Adds base fields (columns) to the new model. --- estate/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models From 7a97626d75c7ae4e32a31f3cd96d28484169cc48 Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Wed, 5 Nov 2025 18:49:19 +0530 Subject: [PATCH 03/19] [IMP] estate: Configure field defaults and UI menus - Set default values for estate.property model fields. - Apply additional field attributes (e.g., readonly, required). - Register menu items to access the tree and form views. --- estate/__init__.py | 1 + estate/__manifest__.py | 12 ++++++++++-- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 3 ++- estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 7 +++++++ 7 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/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 a700d35d54a..5f0ac3b55de 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,5 +2,13 @@ 'name': 'Real Estate', 'depends': ['base'], 'license': 'AGPL-3', - 'author': 'Odoo S.A.' -} \ No newline at end of file + 'application':True, + 'installable':True, + 'author': 'Odoo S.A.', + 'category':'Tutorials', + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + ], +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property \ No newline at end of file +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 348849f0867..0514ce8e6ac 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -22,4 +22,5 @@ class EstateProperty(models.Model): ('south', 'South'), ('east', 'East'), ('west', 'West'), - ]) \ No newline at end of file + ]) + \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0d9c11cc7e4 --- /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 +estsate.access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..c97f087da06 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..4d544c18597 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate.property + list,form + + From 8a7e9f8130c9a82416d942037ea5832ab6557ec4 Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Thu, 6 Nov 2025 11:19:16 +0530 Subject: [PATCH 04/19] [IMP] estate : Added list & form view - added basic, list and form view - also added filter for new or offer received stage --- estate/models/estate_property.py | 14 +++++- estate/views/estate_property_views.xml | 66 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0514ce8e6ac..40d3111a444 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -10,8 +10,8 @@ class EstateProperty(models.Model): pincode = fields.Char() date_availability = fields.Date() expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -23,4 +23,14 @@ class EstateProperty(models.Model): ('east', 'East'), ('west', 'West'), ]) + active=fields.Boolean(default=False) + state=fields.Selection( + selection=[ + ('new','New'), + ('offer_received','Offer Received'), + ('offer_accepted','Offer Accepted'), + ('sold','Sold'), + ('cancelled','Cancelled') + ] + ) \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4d544c18597..6ae098b3fd8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,7 +1,73 @@ + + estate.property + + + + + + + + + + + + + + + estate.property + + + + + + + + + + + + + + estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Properties estate.property list,form + {"search_default_available":1}
From d53dbb080f541f16ac2f389a4961a7373570023a Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Thu, 6 Nov 2025 13:04:10 +0530 Subject: [PATCH 05/19] [FIX] estate : mismatch field name - pincode feild name had spelling mistake in views/estate_property_views.xml --- estate/views/estate_property_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 6ae098b3fd8..da392ac20d4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -19,7 +19,7 @@ - + From 0f0486751c220ca584798d1355645f8861609a9b Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Thu, 6 Nov 2025 14:16:00 +0530 Subject: [PATCH 06/19] [FIX] estate: indentation error --- estate/__init__.py | 1 + estate/__manifest__.py | 5 +++-- estate/models/__init__.py | 1 + estate/models/estate_property.py | 16 ++++++++-------- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 899bcc97f0f..7c378d64614 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1,3 @@ from . import models + diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 5f0ac3b55de..3a771e0ba17 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,8 +2,8 @@ 'name': 'Real Estate', 'depends': ['base'], 'license': 'AGPL-3', - 'application':True, - 'installable':True, + 'application': True, + 'installable': True, 'author': 'Odoo S.A.', 'category':'Tutorials', 'data': [ @@ -12,3 +12,4 @@ 'views/estate_menus.xml', ], } + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..c5006b18cf8 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,2 @@ from . import estate_property + diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 40d3111a444..9da8ed6d254 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -23,14 +23,14 @@ class EstateProperty(models.Model): ('east', 'East'), ('west', 'West'), ]) - active=fields.Boolean(default=False) - state=fields.Selection( + active=fields.Boolean(default= False) + state=fields.Selection( 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') ] ) - \ No newline at end of file + \ No newline at end of file From 167babd0f611c187e1a5eefb7991713351ea8558 Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Thu, 6 Nov 2025 16:56:14 +0530 Subject: [PATCH 07/19] [IMP] estate: Implement list, form, and search views with grouping functionality -Developed and integrated list, form, and search views for the Real Estate module -Including support for group-by operations. --- estate/__init__.py | 1 - estate/models/__init__.py | 1 - estate/models/estate_property.py | 46 +++++----- estate/security/ir.model.access.csv | 4 +- estate/views/estate_menus.xml | 24 ++++-- estate/views/estate_property_views.xml | 113 ++++++++++++++----------- 6 files changed, 111 insertions(+), 78 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 7c378d64614..899bcc97f0f 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,3 +1,2 @@ from . import models - diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c5006b18cf8..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,2 +1 @@ from . import estate_property - diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9da8ed6d254..d40f37799ab 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import models, fields +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): @@ -6,31 +7,34 @@ class EstateProperty(models.Model): _description = "Real Estate Property" name = fields.Char(required=True) - description = fields.Text() - pincode = fields.Char() - date_availability = fields.Date() + description = fields.Text("Description") + postcode = fields.Char("Postcode", required=True) + date_availability = fields.Date("Availability Date", default=fields.Date.today()+relativedelta(months=3)) expected_price = fields.Float(required=True) - selling_price = fields.Float(readonly=True, copy=False) - bedrooms = fields.Integer(default=2) - living_area = fields.Integer() - facades = fields.Integer() - garage = fields.Boolean() - garden = fields.Boolean() - garden_area = fields.Integer(string="Garden Area (sqft)") + selling_price = fields.Float("Selling Price", readonly=True) + bedrooms = fields.Integer("Bedrooms", default=2) + living_area = fields.Integer("living_area(sqm)") + facades = fields.Integer("Facades") + garage = fields.Boolean("Garage") + garden = fields.Boolean("Garden") + garden_area = fields.Integer("Garden Area sqm") garden_orientation = fields.Selection([ ('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West'), ]) - active=fields.Boolean(default= False) - state=fields.Selection( - selection=[ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled') - ] - ) - \ No newline at end of file + state = fields.Selection( + [ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Canceled'), + ], + string="Status", + required=True, + copy=False, + default='new' + ) + \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0d9c11cc7e4..3b6948e942e 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 -estsate.access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index c97f087da06..d1826527722 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,21 @@ - - - + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index da392ac20d4..560d0b61dc5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,73 +1,90 @@ + - - estate.property - - - - - - - - - - - - + - + + estate.property.list estate.property - + - - - - - + - + + Properties (List) + estate.property + list + + + + + estate.property.form estate.property -
+ - +

+ +

+ - + - - + + + + + + + - - - - - - - - - - - - - - - -
- - Properties + + Property(Form) estate.property - list,form - {"search_default_available":1} + form + + + + + + estate.property.search + estate.property + + + + + + + + + + + -
+ + + + + + Properties + estate.property + list,form + + + + \ No newline at end of file From 0001158adbdcd6e632a4b1b01727ab44b6574695 Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Fri, 7 Nov 2025 17:42:44 +0530 Subject: [PATCH 08/19] [IMP] estate: Enhanced Data Models and Functionalities -Introduced Property Type and Property Tag models. -Added Buyer and Salesperson fields. -Implemented the Property Offer model. --- estate/__manifest__.py | 6 +- estate/models/__init__.py | 3 + estate/models/estate_property.py | 55 ++++--- estate/models/estate_property_offer.py | 15 ++ estate/models/estate_property_tag.py | 8 + estate/models/estate_property_type.py | 8 + estate/security/ir.model.access.csv | 7 +- estate/views/estate_menus.xml | 37 +++-- estate/views/estate_property_offer_views.xml | 29 ++++ estate/views/estate_property_tag_views.xml | 8 + estate/views/estate_property_type_views.xml | 7 + estate/views/estate_property_views.xml | 149 +++++++++++-------- 12 files changed, 224 insertions(+), 108 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 3a771e0ba17..c320c2c9918 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,7 +9,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_menus.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_menus.xml' ], } - diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d40f37799ab..412fa33fb9e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -7,34 +7,43 @@ class EstateProperty(models.Model): _description = "Real Estate Property" name = fields.Char(required=True) - description = fields.Text("Description") - postcode = fields.Char("Postcode", required=True) - date_availability = fields.Date("Availability Date", default=fields.Date.today()+relativedelta(months=3)) - expected_price = fields.Float(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date("Availability Date", default=fields.Date.today() + relativedelta(months=3)) + expected_price = fields.Float("Expected Price", required=True) selling_price = fields.Float("Selling Price", readonly=True) - bedrooms = fields.Integer("Bedrooms", default=2) - living_area = fields.Integer("living_area(sqm)") - facades = fields.Integer("Facades") - garage = fields.Boolean("Garage") - garden = fields.Boolean("Garden") - garden_area = fields.Integer("Garden Area sqm") - garden_orientation = fields.Selection([ - ('north', 'North'), - ('south', 'South'), - ('east', 'East'), - ('west', 'West'), - ]) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer("Living Area(sqm)") + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer("Garden Area(sqm)") + garden_orientation = fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], + string="Garden Orientation", + ) state = fields.Selection( [ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('canceled', 'Canceled'), + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), ], string="Status", required=True, copy=False, - default='new' + default="new", ) - \ No newline at end of file + active = fields.Boolean(default=True) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + salesperson_id = fields.Many2one( + "res.users", string="Salesperson") + tag_ids = fields.Many2many("estate.property.tag", string="Tags") + 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..daa0544c947 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,15 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + price = fields.Float() + status = fields.Selection( + [("accepted", "Accepted"), ("refused", "Refused")], + copy=False, + ) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True) + \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..198c1037c41 --- /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 Property Tag" + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..ced40ef01cc --- /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 = "Real Estate Property Type" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 3b6948e942e..58f5c8d8fe6 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 -estate.access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,estate.property.tag.access,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,estate.property.offer.access,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 d1826527722..61b729e178c 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,21 +1,26 @@ + - + - - - + - + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..1fbb3be50a0 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,29 @@ + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..a5c5c0714cc --- /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..382925bd424 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,7 @@ + + + Property Types + estate.property.type + list,form + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 560d0b61dc5..f83c441c093 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,90 +1,109 @@ - - + + Properties + estate.property + list,form + +

+ Create a new property +

+
+
- - estate.property.list + + estate.property.view.list estate.property - - - + + + + + + + + + + - - Properties (List) - estate.property - list - - - - - estate.property.form + + estate.property.view.form estate.property -
+ -

- -

- - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- - Property(Form) - estate.property - form - - - - - - estate.property.search + + estate.property.view.search estate.property - - - - - - - - + + + + + + + - - - - - - Properties - estate.property - list,form - - - -
\ No newline at end of file + From dbb2ea339d604089bebbcded203e29da0421dbcf Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Mon, 10 Nov 2025 18:43:05 +0530 Subject: [PATCH 09/19] [IMP] estate: added computed fields,inverse fun & onchange -Added computed fields with inverse functions and onchange methods, -Improved real estate module with sold and cancel buttons to enhance the module functionality. --- estate/__init__.py | 1 - estate/__manifest__.py | 5 +- estate/models/estate_property.py | 93 ++++++++++++++++++-- estate/models/estate_property_offer.py | 39 +++++++- estate/models/estate_property_tag.py | 5 ++ estate/models/estate_property_type.py | 5 ++ estate/security/ir.model.access.csv | 2 +- estate/views/estate_property_offer_views.xml | 13 ++- estate/views/estate_property_tag_views.xml | 3 +- estate/views/estate_property_views.xml | 19 ++-- 10 files changed, 154 insertions(+), 31 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 c320c2c9918..04ed3321c83 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,11 +1,10 @@ { 'name': 'Real Estate', 'depends': ['base'], - 'license': 'AGPL-3', 'application': True, 'installable': True, 'author': 'Odoo S.A.', - 'category':'Tutorials', + 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', @@ -14,4 +13,6 @@ 'views/estate_property_offer_views.xml', 'views/estate_menus.xml' ], + } + diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 412fa33fb9e..04e9ec0b3d6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,7 @@ -from odoo import models, fields from dateutil.relativedelta import relativedelta +from odoo import models, fields, api +from odoo.exceptions import UserError, ValidationError +from odoo.tools.float_utils import float_compare, float_is_zero class EstateProperty(models.Model): @@ -9,15 +11,18 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date("Availability Date", default=fields.Date.today() + relativedelta(months=3)) + date_availability = fields.Date( + "Availability Date", + default=lambda self: fields.Date.today() + relativedelta(months=3) + ) expected_price = fields.Float("Expected Price", required=True) selling_price = fields.Float("Selling Price", readonly=True) bedrooms = fields.Integer(default=2) - living_area = fields.Integer("Living Area(sqm)") + living_area = fields.Integer("Living Area (sqm)") facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer("Garden Area(sqm)") + garden_area = fields.Integer("Garden Area (sqm)") garden_orientation = fields.Selection( selection=[ ("north", "North"), @@ -41,9 +46,81 @@ class EstateProperty(models.Model): default="new", ) active = fields.Boolean(default=True) - property_type_id = fields.Many2one("estate.property.type", string="Property Type") - buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) - salesperson_id = fields.Many2one( - "res.users", string="Salesperson") + property_type_id = fields.Many2one("estate.property.type", "Property Type") + buyer_id = fields.Many2one("res.partner", "Buyer", copy=False) + salesperson_id = fields.Many2one("res.users", string="Salesperson") tag_ids = fields.Many2many("estate.property.tag", string="Tags") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + total_area = fields.Integer("Total Area (sqm)", compute="_compute_total_area") + best_price = fields.Float("Best Offer", compute="_compute_best_price") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = (record.living_area or 0) + (record.garden_area or 0) + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped("price") or [0]) + + @api.onchange("garden") + def _onchange_garden(self): + for record in self: + if record.garden: + if record.garden_area == 10: + record.garden_orientation = "north" + elif record.garden_area == 20: + record.garden_orientation = "south" + elif record.garden_area == 30: + record.garden_orientation = "east" + elif record.garden_area == 40: + record.garden_orientation = "west" + else: + record.garden_area = 0 + record.garden_orientation = False + else: + record.garden_area = 0 + record.garden_orientation = False + + def action_cancel(self): + for record in self: + if record.state == "sold": + raise UserError("A sold property cannot be cancelled") + record.state = "cancelled" + + def action_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("A cancelled property cannot be set as sold") + record.state = "sold" + + _sql_constraints = [ + ( + "check_expected_price", + "CHECK(expected_price > 0)", + "The expected price must be strictly positive.", + ), + ( + "check_selling_price_positive", + "CHECK(selling_price >= 0)", + "The selling price must be positive.", + ), + ] + + @api.constrains("selling_price", "expected_price") + def _check_selling_price_ratio(self): + for record in self: + if not float_is_zero(record.selling_price, precision_digits=2): + if ( + float_compare( + record.selling_price, + record.expected_price * 0.9, + precision_digits=2, + ) + < 0 + ): + raise ValidationError( + "The selling price cannot be lower than 90% of the expected price." + ) + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index daa0544c947..a495e6135e2 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,7 @@ -from odoo import fields, models +from dateutil.relativedelta import relativedelta + +from odoo import fields, models, api +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -12,4 +15,36 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one("res.partner", string="Partner", required=True) property_id = fields.Many2one("estate.property", string="Property", required=True) - \ No newline at end of file + validity = fields.Integer(string="Validity(days)", default=7) + date_deadline = fields.Date(string="Deadline Date", compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + @api.depends('validity') + def _compute_date_deadline(self): + for record in self: + creation_date = record.create_date or fields.Date.today() + record.date_deadline = relativedelta(days=record.validity) + creation_date + + def _inverse_date_deadline(self): + for record in self: + creation_date = record.create_date or fields.Date.today() + record.validity = (record.date_deadline - fields.Date.to_date(creation_date)).days + + def action_accept(self): + for record in self: + if record.property_id.buyer_id: + raise UserError("Property already accepted") + else: + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.state = 'offer_accepted' + record.property_id.buyer_id= record.partner_id + + def action_refuse(self): + for record in self: + record.status = 'refused' + return True + + _check_offer_price = models.Constraint( + 'CHECK(price > 0)', + 'The price of an offer must be strictly positive.' + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 198c1037c41..04b06249ebe 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,3 +6,8 @@ class EstatePropertyTag(models.Model): _description = "Real Estate Property Tag" name = fields.Char(required=True) + + _check_tag_name_unique = models.Constraint( + 'UNIQUE(name)', + 'The name of the property tag must be unique.' + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index ced40ef01cc..9dbc82c2afe 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,3 +6,8 @@ class EstatePropertyType(models.Model): _description = "Real Estate Property Type" name = fields.Char(required=True) + + _check_type_name_unique = models.Constraint( + 'UNIQUE(name)', + 'The name of the property type must be unique.' + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 58f5c8d8fe6..eae1ee27c92 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_tag,estate.property.tag.access,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer,estate.property.offer.access,model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_offer,estate.property.offer.access,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 1fbb3be50a0..1024a9c4787 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,10 @@ + + + + @@ -29,15 +48,4 @@ - - - estate.property.type.list - estate.property.type - - - - - - - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 34131fc6b58..f2ce15a0eb8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -61,13 +61,6 @@ - - - - - - - From de6b813c38b536248af9db8b9e86ba0f63de976c Mon Sep 17 00:00:00 2001 From: haahi-odoo Date: Mon, 17 Nov 2025 15:41:25 +0530 Subject: [PATCH 14/19] [IMP] estate : Added invoicing for sold properties and Kanban views -Added two invoice function during invoice creation for sold properties -One for 6% commission for sales and another for fixed charges of 100. -Added a Kanban view to improve property visualization in the Real Estate module. --- estate/estate_account/__init__.py | 1 + estate/estate_account/__manifest__.py | 8 +++++ estate/estate_account/models/__init__.py | 0 .../estate_account/models/estate_property.py | 28 +++++++++++++++ estate/models/estate_property.py | 20 ++--------- estate/models/estate_property_offer.py | 36 +++++-------------- estate/models/estate_property_tag.py | 3 +- estate/models/estate_property_type.py | 11 +++--- estate/models/res_users.py | 6 ++++ estate/views/estate_property_offer_views.xml | 8 ----- estate/views/estate_property_tag_views.xml | 1 - estate/views/estate_property_type_views.xml | 2 -- estate/views/estate_property_views.xml | 23 ++++++++---- estate/views/res_users_views.xml | 14 ++++++++ 14 files changed, 91 insertions(+), 70 deletions(-) create mode 100644 estate/estate_account/__init__.py create mode 100644 estate/estate_account/__manifest__.py create mode 100644 estate/estate_account/models/__init__.py create mode 100644 estate/estate_account/models/estate_property.py create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_views.xml diff --git a/estate/estate_account/__init__.py b/estate/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/estate_account/__manifest__.py b/estate/estate_account/__manifest__.py new file mode 100644 index 00000000000..3269b947f02 --- /dev/null +++ b/estate/estate_account/__manifest__.py @@ -0,0 +1,8 @@ +{ + 'name': 'Estate Account', + 'application': True, + 'installable': True, + 'author': 'Odoo S.A.', + 'license': 'LGPL-3' + 'depends': ['estate', 'account'], +} diff --git a/estate/estate_account/models/__init__.py b/estate/estate_account/models/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/estate_account/models/estate_property.py b/estate/estate_account/models/estate_property.py new file mode 100644 index 00000000000..af71b8b0f4d --- /dev/null +++ b/estate/estate_account/models/estate_property.py @@ -0,0 +1,28 @@ +from odoo import Command, models + + +class EstateProperty(models.Model): + _inherit="estate.property" + + def action_sold(self): + self.env['account.move'].create({ + 'partner_id': self.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create( + { + "name": "selling price commission (6%)", + "quantity": 1, + "price_unit": self.selling_price * 0.06 , + } + ), + Command.create( + { + "name": "Fixed 100 fee", + "quantity": 1, + "price_unit": 100, + } + ) + ], + }) + return super().action_sold() diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7c9142abda0..8adb31a708d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,24 +9,19 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" _order = "id desc" - name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date( "Availability Date", default=lambda self: fields.Date.today() + relativedelta(months=3), ) - expected_price = fields.Float("Expected Price", required=True) selling_price = fields.Float("Selling Price", readonly=True) - bedrooms = fields.Integer(default=2) living_area = fields.Integer("Living Area(sqft)") facades = fields.Integer() garage = fields.Boolean() - garden = fields.Boolean() garden_area = fields.Integer("Garden Area(sqft)") garden_orientation = fields.Selection( @@ -38,7 +33,6 @@ class EstateProperty(models.Model): ], string="Garden Orientation", ) - state = fields.Selection( [ ("new", "New"), @@ -52,21 +46,16 @@ class EstateProperty(models.Model): copy=False, default="new", ) - active = fields.Boolean(default=True) - property_type_id = fields.Many2one("estate.property.type", "Property Type") buyer_id = fields.Many2one("res.partner", "Buyer", copy=False) salesperson_id = fields.Many2one("res.users", string="Salesperson") - tag_ids = fields.Many2many("estate.property.tag", string="Tags") - offer_ids = fields.One2many( "estate.property.offer", "property_id", string="Offers" ) - total_area = fields.Integer("Total Area(sqm)", compute="_compute_total_area") best_price = fields.Float("Best Offer", compute="_compute_best_price") @@ -75,17 +64,16 @@ class EstateProperty(models.Model): 'The expected price of a property must be strictly positive.', ) - @api.depends("living_area", "garden_area") def _compute_total_area(self): for record in self: record.total_area = (record.living_area or 0) + (record.garden_area or 0) - + @api.depends("offer_ids.price") def _compute_best_price(self): for record in self: record.best_price = max(record.offer_ids.mapped("price"), default=0) - + @api.onchange("garden") def _onchange_garden(self): if self.garden: @@ -94,19 +82,17 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False - def action_cancel(self): for record in self: if record.state == "sold": raise UserError("A sold property cannot be cancelled") record.state = "cancelled" - def action_sold(self): for record in self: if record.state == "cancelled": raise UserError("A cancelled property cannot be set as sold") record.state = "sold" - + @api.constrains("selling_price", "expected_price") def _check_selling_price(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 3c779c0f833..0cf9387313f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ from dateutil.relativedelta import relativedelta + from odoo import fields, models, api from odoo.exceptions import UserError @@ -7,7 +8,6 @@ class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Real Estate Property Offer" _order = "price desc" - price = fields.Float() status = fields.Selection( [("accepted", "Accepted"), ("refused", "Refused")], @@ -21,79 +21,59 @@ class EstatePropertyOffer(models.Model): compute="_compute_date_deadline", inverse="_inverse_date_deadline" ) - property_type_id = fields.Many2one( related="property_id.property_type_id", store=True ) + _check_offer_price = models.Constraint( + 'CHECK(price > 0)', + 'The price of an offer must be strictly positive.' + ) + @api.depends('validity') def _compute_date_deadline(self): for record in self: creation_date = record.create_date or fields.Date.today() record.date_deadline = creation_date + relativedelta(days=record.validity) - def _inverse_date_deadline(self): for record in self: - creation_date = record.create_date or fields.Date.today() + creation_date = record.create_date.date() or fields.Date.today() record.validity = (record.date_deadline - creation_date).days - def action_accept(self): for record in self: if record.property_id.buyer_id: raise UserError("Property already has an accepted offer.") - record.status = 'accepted' record.property_id.selling_price = record.price record.property_id.state = 'offer_accepted' record.property_id.buyer_id = record.partner_id - def action_refuse(self): self.status = 'refused' return True - + @api.model def create(self, vals): - """ - Handles: - > avoid search inside loop - > validate in bulk - > update property state - """ - vals_list = vals if isinstance(vals, list) else [vals] - property_offer_map = {} for v in vals_list: pid = v.get("property_id") if pid: property_offer_map.setdefault(pid, []).append(v) - for pid, offers in property_offer_map.items(): prices = [o.get("price") for o in offers if o.get("price") is not None] - if prices: max_new_price = max(prices) - existing_offers = self.search([ ("property_id", "=", pid), ("price", ">=", max_new_price), ], limit=1) - if existing_offers: raise UserError( "You cannot create an offer with a lower amount than an existing offer for this property." ) - records = super().create(vals) - if isinstance(records, models.Model): for offer in records: offer.property_id.state = "offer_received" - return records - - _check_offer_price = models.Constraint( - 'CHECK(price > 0)', - 'The price of an offer must be strictly positive.' - ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index f51c0053720..096b572d08c 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -5,10 +5,9 @@ class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Real Estate Property Tag" _order = "name" - name = fields.Char(required=True) color = fields.Integer() - + _check_tag_name_unique = models.Constraint( 'UNIQUE(name)', 'The name of the property tag must be unique.' diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index fe058f1d73c..7ff557fa228 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -5,19 +5,18 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Real Estate Property Type" _order = "sequence, name" - name = fields.Char(required=True) property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") sequence = fields.Integer("Sequence", default=1) offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") offer_count = fields.Integer(string="Offer Count", compute="_compute_offer_count") - @api.depends("offer_ids") - def _compute_offer_count(self): - for record in self: - record.offer_count = len(record.offer_ids) - _check_type_name_unique = models.Constraint( 'UNIQUE(name)', 'The name of the property type must be unique.' ) + + @api.depends("offer_ids") + def _compute_offer_count(self): + for record in self: + record.offer_count = len(record.offer_ids) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..86067ad792c --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,6 @@ +from odoo import models, fields + + +class InheritedModel(models.Model): + _inherit = "res.users" + property_ids=fields.One2Many("estate.property","salesperson_id",domain=[("state","!=","sold")], string="Properties") diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 1d948fdf735..edf1a505064 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,13 +1,10 @@ - Property Offers estate.property.offer list,form [('property_type_id', '=', active_id)] - - estate.property.offer.list estate.property.offer @@ -15,7 +12,6 @@ - @@ -25,8 +21,6 @@ - - estate.property.offer.form estate.property.offer @@ -42,7 +36,6 @@ -