From a80ef9784e31ca3074dbdf41071e99e6de02cf5e Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Thu, 5 Feb 2026 18:03:22 +0530 Subject: [PATCH 01/19] [ADD] estate: module skeleton --- estate/__init__.py | 1 + estate/__manifest__.py | 14 ++++++++++++++ 2 files changed, 15 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..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..f1d286850c8 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,14 @@ +{ + "name": "Real Estate", + "version": "1.0", + "category": "Tutorial", + "summary": "Real Estate Advertisement Module", + "author": "Your Name", + "license": "LGPL-3", + "depends": ["base"], + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + ], + "application": True, +} From db2e80cb9485c89096668273a4d30365fa6ee05f Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Thu, 5 Feb 2026 18:03:56 +0530 Subject: [PATCH 02/19] [ADD] estate: basic property model --- estate/models/__init__.py | 1 + estate/models/estate_property.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..afcc3192226 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,29 @@ +from odoo import models, fields + + +class EstateProperty(models.Model): + """Real estate property.""" + + _name = "estate.property" + _description = "Estate Property" + + name = fields.Char(string="Title", required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + [ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] + ) From 8e034258508bdbbb9f2b0ca91dbe029f1b964f8b Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Thu, 5 Feb 2026 18:05:03 +0530 Subject: [PATCH 03/19] [ADD] estate: basic property views --- estate/views/estate_property_views.xml | 58 ++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..6230547ee47 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,58 @@ + + + + + estate.property.view.list + estate.property + + + + + + + + + + + + estate.property.view.form + estate.property + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + Properties + estate.property + list,form + + + + + +
From 472a467fab2bd6cc5804b4d92dede45668713683 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Thu, 5 Feb 2026 18:06:00 +0530 Subject: [PATCH 04/19] [ADD] estate: basic security rules --- estate/security/ir.model.access.csv | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..f2146249f72 --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,estate.property,model_estate_property,,1,1,1,1 From 4a2cb03ba985d5df5b577e291d9b410276a9a488 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Fri, 6 Feb 2026 18:35:16 +0530 Subject: [PATCH 05/19] [IMP] estate: add menus, actions, and custom list/form/search views --- estate/__manifest__.py | 6 ++-- estate/models/estate_property.py | 34 +++++++++++++++--- estate/security/estate_security.xml | 15 ++++++++ estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 12 +++++++ estate/views/estate_property_views.xml | 49 +------------------------- 6 files changed, 63 insertions(+), 55 deletions(-) create mode 100644 estate/security/estate_security.xml create mode 100644 estate/views/estate_menus.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f1d286850c8..8bdd29bb1bf 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,14 +1,16 @@ { "name": "Real Estate", "version": "1.0", - "category": "Tutorial", + "category": "Tutorials", "summary": "Real Estate Advertisement Module", - "author": "Your Name", + "author": "Prasad Venkat", "license": "LGPL-3", "depends": ["base"], "data": [ + "security/estate_security.xml", "security/ir.model.access.csv", "views/estate_property_views.xml", + "views/estate_menus.xml", ], "application": True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index afcc3192226..737c7b05da2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,24 +1,35 @@ from odoo import models, fields +from datetime import timedelta class EstateProperty(models.Model): - """Real estate property.""" - _name = "estate.property" _description = "Estate Property" name = fields.Char(string="Title", required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + + date_availability = fields.Date( + copy=False, + default=lambda self: fields.Date.today() + timedelta(days=90) + ) + expected_price = fields.Float(required=True) - selling_price = fields.Float(readonly=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() + garden_orientation = fields.Selection( [ ("north", "North"), @@ -27,3 +38,18 @@ class EstateProperty(models.Model): ("west", "West"), ] ) + + active = fields.Boolean(default=True) + + state = fields.Selection( + [ + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + required=True, + copy=False, + default="new", + ) diff --git a/estate/security/estate_security.xml b/estate/security/estate_security.xml new file mode 100644 index 00000000000..dc8eb6f4c14 --- /dev/null +++ b/estate/security/estate_security.xml @@ -0,0 +1,15 @@ + + + + + + Estate User + + + + + Estate Manager + + + + diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index f2146249f72..03ad451a8d5 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,estate.property,model_estate_property,,1,1,1,1 +access_estate_property_user,estate.property user,model_estate_property,estate.group_estate_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..abe91aecb35 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 6230547ee47..8ebeabf2d68 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,58 +1,11 @@ - - estate.property.view.list - estate.property - - - - - - - - - - - - estate.property.view.form - estate.property - -
- - - - - - - - - - - - - - - - - - - -
-
-
- + Properties estate.property list,form - - -
From 210866101f8ed9c9fb2a019091bf547b033c8cc1 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Mon, 9 Feb 2026 17:52:54 +0530 Subject: [PATCH 06/19] [FIX] estate: clean up access rules and add property views --- estate/__manifest__.py | 2 +- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 28 ++++--- estate/views/estate_property_views.xml | 102 ++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 13 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 8bdd29bb1bf..a332535392a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,10 +7,10 @@ "license": "LGPL-3", "depends": ["base"], "data": [ - "security/estate_security.xml", "security/ir.model.access.csv", "views/estate_property_views.xml", "views/estate_menus.xml", ], "application": True, + 'installable': True, } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 03ad451a8d5..f2146249f72 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_user,estate.property user,model_estate_property,estate.group_estate_user,1,1,1,1 +access_estate_property,estate.property,model_estate_property,,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index abe91aecb35..66e002c5adf 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,12 +1,20 @@ - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8ebeabf2d68..c2d5cf751c6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,11 +1,111 @@ - + Properties estate.property list,form + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + + + + +
From 1d20518af82392d8e913bbdbd030b8ccb589e7fc Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Tue, 10 Feb 2026 17:57:07 +0530 Subject: [PATCH 07/19] [ADD] estate: property types, tags and offers --- estate/__manifest__.py | 4 +- estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 52 +++++++++++++++----- estate/models/estate_property_offer.py | 27 ++++++++++ estate/models/estate_property_tag.py | 7 +++ estate/models/estate_property_type.py | 7 +++ estate/security/ir.model.access.csv | 5 ++ estate/views/estate_menus.xml | 25 +++++++++- estate/views/estate_property_offer_views.xml | 33 +++++++++++++ estate/views/estate_property_tag_views.xml | 35 +++++++++++++ estate/views/estate_property_type_views.xml | 37 ++++++++++++++ estate/views/estate_property_views.xml | 26 ++++++++++ 12 files changed, 246 insertions(+), 15 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 a332535392a..4857a89597b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,9 +8,11 @@ "depends": ["base"], "data": [ "security/ir.model.access.csv", + "views/estate_property_type_views.xml", "views/estate_property_views.xml", + "views/estate_property_tag_views.xml", "views/estate_menus.xml", ], "application": True, - 'installable': True, + "installable": True, } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..09b2099fe84 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 \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 737c7b05da2..934ed281e66 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,26 +1,23 @@ -from odoo import models, fields from datetime import timedelta - +from odoo import fields, models class EstateProperty(models.Model): _name = "estate.property" - _description = "Estate Property" + _description = "Real Estate Property" - name = fields.Char(string="Title", required=True) + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() + + date_availability = fields.Date( + default=lambda self: fields.Date.today() + timedelta(days=90), copy=False, - default=lambda self: fields.Date.today() + timedelta(days=90) ) expected_price = fields.Float(required=True) - - selling_price = fields.Float( - readonly=True, - copy=False - ) + selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) living_area = fields.Integer() @@ -29,7 +26,6 @@ class EstateProperty(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() - garden_orientation = fields.Selection( [ ("north", "North"), @@ -49,7 +45,39 @@ class EstateProperty(models.Model): ("sold", "Sold"), ("cancelled", "Cancelled"), ], + default="new", required=True, copy=False, - default="new", ) + + property_type_id = fields.Many2one( + 'estate.property.type', + string="Property Type", + ) + + + + buyer_id = fields.Many2one( + 'res.partner', + string="Buyer", + copy=False, + readonly=True, + ) + + user_id = fields.Many2one( + 'res.users', + string="Salesperson", + default=lambda self: self.env.user, + ) + tag_ids = fields.Many2many( + 'estate.property.tag', + string='Tags' + ) + offer_ids = fields.One2many( + 'estate.property.offer', + 'property_id', + string="Offers", + ) + + + \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..91d17bfaca2 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,27 @@ +from odoo import models, fields + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + + price = fields.Float(string="Offer Price") + + status = fields.Selection( + [ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + copy=False, + ) + + partner_id = fields.Many2one( + 'res.partner', + string="Buyer", + required=True, + ) + + property_id = fields.Many2one( + 'estate.property', + string="Property", + required=True, + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..24beb0208d7 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = '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..e0d9267df9f --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = '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 f2146249f72..30e7b080460 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,estate.property,model_estate_property,,1,1,1,1 +access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,Estate Property Offer User,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 66e002c5adf..034c1d7ef8a 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,14 +1,12 @@ - - + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..1365e98e978 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,33 @@ + + + + + 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..025f20d2104 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,35 @@ + + + + Property Tags + estate.property.tag + list,form + + + + + estate.property.tag.list + estate.property.tag + + + + + + + + + + estate.property.tag.form + estate.property.tag + +
+ + + + + +
+
+
+ +
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..9b54a0c56ee --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,37 @@ + + + + + Property Types + estate.property.type + list,form + + + + + + estate.property.type.list + estate.property.type + + + + + + + + + + estate.property.type.form + estate.property.type + +
+ + + + + +
+
+
+ +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c2d5cf751c6..c242aeda859 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -43,6 +43,7 @@ + @@ -57,6 +58,10 @@ + + + + @@ -68,8 +73,29 @@ + + + + + + + + + + + + + + + + + + + + + From 42c643cab0719532167ea36d2ae67ce383379e15 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Tue, 10 Feb 2026 18:30:53 +0530 Subject: [PATCH 08/19] [ADD] estate: property types, tags and offers --- estate/__manifest__.py | 1 + estate/models/estate_property.py | 34 ++++++-------------------- estate/models/estate_property_offer.py | 10 +++++--- estate/models/estate_property_tag.py | 6 +++-- estate/models/estate_property_type.py | 6 +++-- estate/security/ir.model.access.csv | 10 +++----- 6 files changed, 27 insertions(+), 40 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4857a89597b..2dc3eab445a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,7 @@ "license": "LGPL-3", "depends": ["base"], "data": [ + "security/estate_security.xml", "security/ir.model.access.csv", "views/estate_property_type_views.xml", "views/estate_property_views.xml", diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 934ed281e66..e9f4ba44424 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,28 +1,23 @@ from datetime import timedelta from odoo import fields, models + + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - - - date_availability = fields.Date( default=lambda self: fields.Date.today() + timedelta(days=90), copy=False, ) - 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() @@ -34,9 +29,7 @@ class EstateProperty(models.Model): ("west", "West"), ] ) - active = fields.Boolean(default=True) - state = fields.Selection( [ ("new", "New"), @@ -49,35 +42,24 @@ class EstateProperty(models.Model): required=True, copy=False, ) - property_type_id = fields.Many2one( - 'estate.property.type', + "estate.property.type", string="Property Type", ) - - - buyer_id = fields.Many2one( - 'res.partner', + "res.partner", string="Buyer", copy=False, readonly=True, ) - user_id = fields.Many2one( - 'res.users', + "res.users", string="Salesperson", default=lambda self: self.env.user, ) - tag_ids = fields.Many2many( - 'estate.property.tag', - string='Tags' - ) + tag_ids = fields.Many2many("estate.property.tag", string="Tags") offer_ids = fields.One2many( - 'estate.property.offer', - 'property_id', + "estate.property.offer", + "property_id", string="Offers", ) - - - \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 91d17bfaca2..aefd69e794e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,7 @@ from odoo import models, fields + + class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Estate Property Offer" @@ -8,20 +10,20 @@ class EstatePropertyOffer(models.Model): status = fields.Selection( [ - ('accepted', 'Accepted'), - ('refused', 'Refused'), + ("accepted", "Accepted"), + ("refused", "Refused"), ], copy=False, ) partner_id = fields.Many2one( - 'res.partner', + "res.partner", string="Buyer", required=True, ) property_id = fields.Many2one( - 'estate.property', + "estate.property", string="Property", required=True, ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 24beb0208d7..b1d965f6dee 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,7 +1,9 @@ from odoo import models, fields + + class EstatePropertyTag(models.Model): - _name = 'estate.property.tag' - _description = 'Estate Property Tag' + _name = "estate.property.tag" + _description = "Estate Property Tag" name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index e0d9267df9f..d88bdde3897 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,7 +1,9 @@ from odoo import models, fields + + class EstatePropertyType(models.Model): - _name = 'estate.property.type' - _description = 'Estate Property Type' + _name = "estate.property.type" + _description = "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 30e7b080460..e7ec57722ea 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,7 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,estate.property,model_estate_property,,1,1,1,1 -access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer_user,Estate Property Offer User,model_estate_property_offer,base.group_user,1,1,1,1 - - +access_estate_property_user,estate.property user,model_estate_property,estate.group_estate_user,1,1,1,1 +access_estate_property_type_user,estate.property.type user,model_estate_property_type,estate.group_estate_user,1,1,1,1 +access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,estate.group_estate_user,1,1,1,1 +access_estate_property_offer_user,estate.property.offer user,model_estate_property_offer,estate.group_estate_user,1,1,1,1 From 9136ec406982e1953bc5812eed1b707b88dfcf46 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Tue, 10 Feb 2026 18:51:25 +0530 Subject: [PATCH 09/19] [IMP] estate: add property types, tags and offers Chapter 7: Relationships between models - Added property types and property tags - Implemented many2one, one2many and many2many relations - Added property offers linked to properties - Integrated offers in property form view --- estate/models/__init__.py | 3 ++- estate/models/estate_property.py | 1 - estate/models/estate_property_offer.py | 3 --- estate/models/estate_property_tag.py | 1 - estate/models/estate_property_type.py | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe84..9752b464357 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,5 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer + diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e9f4ba44424..aeec7209e28 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,7 +2,6 @@ from odoo import fields, models - class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index aefd69e794e..8f3d2a35c6d 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,7 +1,6 @@ from odoo import models, fields - class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Estate Property Offer" @@ -15,13 +14,11 @@ class EstatePropertyOffer(models.Model): ], copy=False, ) - partner_id = fields.Many2one( "res.partner", string="Buyer", required=True, ) - property_id = fields.Many2one( "estate.property", string="Property", diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index b1d965f6dee..78ce298bce9 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,7 +1,6 @@ from odoo import models, fields - class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Estate Property Tag" diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index d88bdde3897..eebc62e998c 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,7 +1,6 @@ from odoo import models, fields - class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Estate Property Type" From 5ee2f9cb44323b2925ae46c29dd726a7a390dff5 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Wed, 11 Feb 2026 11:01:42 +0530 Subject: [PATCH 10/19] [FIX] estate: flake8 warning (W391) --- estate/models/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 9752b464357..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,4 +2,3 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer - From efcf8bcc5abc5626ed2352465bcafafa09f90a48 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Wed, 11 Feb 2026 18:24:41 +0530 Subject: [PATCH 11/19] [REM] estate: remove custom security groups and cleanup --- estate/__manifest__.py | 1 - estate/security/estate_security.xml | 15 ---------- estate/security/ir.model.access.csv | 8 ++--- estate/views/estate_property_offer_views.xml | 5 ---- estate/views/estate_property_views.xml | 31 -------------------- 5 files changed, 4 insertions(+), 56 deletions(-) delete mode 100644 estate/security/estate_security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 2dc3eab445a..4857a89597b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,7 +7,6 @@ "license": "LGPL-3", "depends": ["base"], "data": [ - "security/estate_security.xml", "security/ir.model.access.csv", "views/estate_property_type_views.xml", "views/estate_property_views.xml", diff --git a/estate/security/estate_security.xml b/estate/security/estate_security.xml deleted file mode 100644 index dc8eb6f4c14..00000000000 --- a/estate/security/estate_security.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - Estate User - - - - - Estate Manager - - - - diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index e7ec57722ea..5e86e3d9f93 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_user,estate.property user,model_estate_property,estate.group_estate_user,1,1,1,1 -access_estate_property_type_user,estate.property.type user,model_estate_property_type,estate.group_estate_user,1,1,1,1 -access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,estate.group_estate_user,1,1,1,1 -access_estate_property_offer_user,estate.property.offer user,model_estate_property_offer,estate.group_estate_user,1,1,1,1 +access_estate_property_user,estate.property user,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,estate.property.type user,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,estate.property.tag user,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,estate.property.offer user,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 1365e98e978..f500b240e6d 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,6 +1,4 @@ - - estate.property.offer.list estate.property.offer @@ -12,8 +10,6 @@ - - estate.property.offer.form estate.property.offer @@ -29,5 +25,4 @@ - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c242aeda859..ec3b4cfa4db 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,15 +1,10 @@ - - Properties estate.property list,form - - - estate.property.list estate.property @@ -25,21 +20,15 @@ - - - estate.property.form estate.property
-

- - @@ -52,7 +41,6 @@ - @@ -89,49 +77,30 @@ - - - - - - -
- - estate.property.search estate.property - - - - - - - - - -
From 65522014a07fb415e8f1196eb3bdb16ed6c78bf1 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Thu, 12 Feb 2026 18:45:32 +0530 Subject: [PATCH 12/19] [IMP] estate: implemented computed fields, inverse logic for offer deadline, and garden onchange --- estate/models/estate_property.py | 44 +++++++++++++++++++- estate/models/estate_property_offer.py | 42 +++++++++++++++++-- estate/views/estate_menus.xml | 43 +++---------------- estate/views/estate_property_offer_views.xml | 18 ++++++++ estate/views/estate_property_tag_views.xml | 5 --- estate/views/estate_property_type_views.xml | 7 ---- estate/views/estate_property_views.xml | 27 ++++++++---- 7 files changed, 122 insertions(+), 64 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index aeec7209e28..b61c3909ded 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,13 @@ from datetime import timedelta -from odoo import fields, models + +from odoo import api,fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() @@ -14,12 +17,14 @@ class EstateProperty(models.Model): ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) - bedrooms = fields.Integer(default=2) + best_price = fields.Float(compute="_compute_best_price") + bedrooms = fields.Integer(default=2,copy=False) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() + total_area = fields.Float(compute="_compute_total_area") garden_orientation = fields.Selection( [ ("north", "North"), @@ -62,3 +67,38 @@ class EstateProperty(models.Model): "property_id", string="Offers", ) + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped("price") + record.best_price = max(prices) if prices else 0.0 + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = False + def action_cancel(self): + for record in self: + if record.state == "sold": + raise UserError("Sold property cannot be cancelled.") + record.state = "cancelled" + return True + def action_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("Cancelled property cannot be sold.") + record.state = "sold" + return True + + + + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8f3d2a35c6d..f2e0dd806fd 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,12 +1,14 @@ -from odoo import models, fields +from datetime import timedelta + +from odoo import api, models, fields +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Estate Property Offer" - + price = fields.Float(string="Offer Price") - status = fields.Selection( [ ("accepted", "Accepted"), @@ -24,3 +26,37 @@ class EstatePropertyOffer(models.Model): string="Property", required=True, ) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + ) + @api.depends("create_date", "validity") + def _compute_date_deadline(self): + for record in self: + create_date = record.create_date.date() if record.create_date else fields.Date.today() + record.date_deadline = create_date + timedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + create_date = record.create_date.date() if record.create_date else fields.Date.today() + record.validity = (record.date_deadline - create_date).days + + def action_accept(self): + for record in self: + # Check if another offer already accepted + accepted_offers = record.property_id.offer_ids.filtered( + lambda o: o.status == "accepted" + ) + if accepted_offers: + raise UserError("Only one offer can be accepted.") + + record.status = "accepted" + + # Set property values + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + + return True + + diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 034c1d7ef8a..1e5c01a3c96 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,41 +1,8 @@ - - - - - - - - - - - + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index f500b240e6d..233e376842a 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,8 @@ + + @@ -15,11 +17,27 @@ estate.property.offer
+
+
+ + +
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 025f20d2104..e37fc3e401b 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -5,8 +5,6 @@ estate.property.tag list,form - - estate.property.tag.list estate.property.tag @@ -16,8 +14,6 @@
- - estate.property.tag.form estate.property.tag @@ -31,5 +27,4 @@
- diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 9b54a0c56ee..960ecb48dff 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -1,14 +1,10 @@ - Property Types estate.property.type list,form - - - estate.property.type.list estate.property.type @@ -18,8 +14,6 @@
- - estate.property.type.form estate.property.type @@ -33,5 +27,4 @@
- diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ec3b4cfa4db..6e0a4708d51 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -26,6 +26,19 @@
+
+

@@ -39,6 +52,7 @@ + @@ -47,7 +61,7 @@ - + @@ -58,18 +72,14 @@ + - - - - - - - + + @@ -94,7 +104,6 @@ From 5a7db48a601588e13a84f554a35fa2dd3d8bf624 Mon Sep 17 00:00:00 2001 From: venpr-odoo Date: Fri, 13 Feb 2026 17:55:32 +0530 Subject: [PATCH 13/19] [IMP] estate: 9th chacpter add property offers and implement accept/refuse logic --- estate/models/estate_property.py | 18 +++-- estate/models/estate_property_offer.py | 79 +++++++++++++++++--- estate/views/estate_menus.xml | 2 +- estate/views/estate_property_offer_views.xml | 26 +++---- estate/views/estate_property_tag_views.xml | 2 +- estate/views/estate_property_views.xml | 30 ++++++-- 6 files changed, 118 insertions(+), 39 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b61c3909ded..990093393aa 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -15,10 +15,10 @@ class EstateProperty(models.Model): default=lambda self: fields.Date.today() + timedelta(days=90), copy=False, ) - expected_price = fields.Float(required=True) + expected_price = fields.Float(required=True, default=0.0) selling_price = fields.Float(readonly=True, copy=False) best_price = fields.Float(compute="_compute_best_price") - bedrooms = fields.Integer(default=2,copy=False) + bedrooms = fields.Integer(default=2, copy=False) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -77,6 +77,7 @@ def _compute_best_price(self): for record in self: prices = record.offer_ids.mapped("price") record.best_price = max(prices) if prices else 0.0 + @api.onchange("garden") def _onchange_garden(self): if self.garden: @@ -85,18 +86,25 @@ 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("Sold property cannot be cancelled.") record.state = "cancelled" return True + def action_sold(self): for record in self: - if record.state == "cancelled": + if record.state == 'cancelled': raise UserError("Cancelled property cannot be sold.") - record.state = "sold" - return True + accepted_offer = record.offer_ids.filtered( + lambda o: o.status == 'accepted' + ) + if not accepted_offer: + raise UserError("You must accept an offer before selling the property.") + record.state = 'sold' + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index f2e0dd806fd..0a07a34a4fa 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -7,14 +7,16 @@ class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Estate Property Offer" - + price = fields.Float(string="Offer Price") status = fields.Selection( [ + ("pending", "Pending"), ("accepted", "Accepted"), ("refused", "Refused"), ], copy=False, + default="pending", ) partner_id = fields.Many2one( "res.partner", @@ -31,6 +33,7 @@ class EstatePropertyOffer(models.Model): compute="_compute_date_deadline", inverse="_inverse_date_deadline", ) + @api.depends("create_date", "validity") def _compute_date_deadline(self): for record in self: @@ -41,22 +44,74 @@ def _inverse_date_deadline(self): for record in self: create_date = record.create_date.date() if record.create_date else fields.Date.today() record.validity = (record.date_deadline - create_date).days + + @api.model_create_multi + def create(self, vals_list): + offers = super().create(vals_list) + for offer in offers: + property_rec = offer.property_id + if property_rec.state in ['offer_accepted', 'sold', 'cancelled']: + raise UserError("Cannot create offer for this property.") + if property_rec.state == 'new': + property_rec.state = 'offer_received' + return offers def action_accept(self): - for record in self: - # Check if another offer already accepted - accepted_offers = record.property_id.offer_ids.filtered( - lambda o: o.status == "accepted" + for offer in self: + if offer.property_id.state in ['sold', 'cancelled']: + raise UserError("You cannot accept an offer on a sold or cancelled property.") + accepted_offer = offer.property_id.offer_ids.filtered( + lambda o: o.status == "accepted" and o != offer ) - if accepted_offers: - raise UserError("Only one offer can be accepted.") + if accepted_offer: + raise UserError("Only one offer can be accepted for a property.") + offer.status = "accepted" + offer.property_id.write({ + 'selling_price': offer.price, + 'buyer_id': offer.partner_id.id, + 'state': 'offer_accepted' + }) - record.status = "accepted" + def action_refuse(self): + for offer in self: + property_rec = offer.property_id + if property_rec.state in ['sold', 'cancelled']: + raise UserError("You cannot refuse an offer on a sold or cancelled property.") + if offer.status == "accepted": + offer.status = "refused" + property_rec.write({ + 'selling_price': 0.0, + 'buyer_id': False, + }) + other_pending = property_rec.offer_ids.filtered( + lambda o: o.status == 'pending' + ) + if other_pending: + property_rec.state = 'offer_received' + else: + property_rec.state = 'new' + else: + offer.status = "refused" - # Set property values - record.property_id.selling_price = record.price - record.property_id.buyer_id = record.partner_id + def unlink(self): + for offer in self: + if offer.property_id.state == 'sold': + raise UserError("You cannot delete an offer of a sold property.") + properties = self.mapped('property_id') + result = super().unlink() + for property_rec in properties: + offers = property_rec.offer_ids + if not offers: + property_rec.write({ + 'state': 'new', + 'buyer_id': False, + 'selling_price': 0.0, + }) + elif any(o.status == 'accepted' for o in offers): + property_rec.state = 'offer_accepted' + else: + property_rec.state = 'offer_received' + return result - return True diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 1e5c01a3c96..a0999342a2e 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -5,4 +5,4 @@ - + \ 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 233e376842a..993df663d44 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,6 +9,16 @@ +