From f41c5a7236213d6cd7950a653194ee661468bcf0 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Fri, 3 Jul 2026 17:00:51 +0530 Subject: [PATCH 01/16] [ADD] estate: add initial module structure Chapter 2: Create the initial Real Estate module for the Server Framework 101 tutorial. This introduces the base module structure with the manifest and package initialization files required for further development. --- estate/__init__.py | 0 estate/__manifest__.py | 10 ++++++++++ estate/models/__init__.py | 0 3 files changed, 10 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.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..765406975fb --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': 'Real Estate', + 'version': '1.0', + 'author': 'yacha', + 'depends': ['base'], + 'application': True, + 'category': 'Tutorials', + 'installable': True, + 'license': 'LGPL-3' +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 489174aaf1359d3267c15979ce3106cabc909139 Mon Sep 17 00:00:00 2001 From: yacha Date: Mon, 6 Jul 2026 17:55:27 +0530 Subject: [PATCH 02/16] [ADD] estate: add property model and basic fields proper Chapter 2: Create the initial property model for the real estate module. Add the basic property information needed to manage properties, including the property name, description, postcode, availability date, expected and selling prices, bedrooms, living area, facades, garage, garden details, and property state. Set appropriate defaults and field behavior for the initial property data structure. --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 estate/models/estate_property.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 index e69de29bb2d..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ 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..78eb2568d09 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import models, fields + + +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() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + 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 14d80a2aedc6bb443e0ce230beaf782944fcbda7 Mon Sep 17 00:00:00 2001 From: yacha Date: Tue, 7 Jul 2026 18:11:29 +0530 Subject: [PATCH 03/16] [ADD] estate: Security Access Granted --- estate/__manifest__.py | 17 +++++++++-------- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 765406975fb..ba0b3ed9c20 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,11 @@ { - 'name': 'Real Estate', - 'version': '1.0', - 'author': 'yacha', - 'depends': ['base'], - 'application': True, - 'category': 'Tutorials', - 'installable': True, - 'license': 'LGPL-3' + "name": "Real Estate", + "version": "1.0", + "author": "yacha", + "depends": ["base"], + "application": True, + "category": "Tutorials", + "installable": True, + "license": "LGPL-3", + "data": ["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..0e11f47e58d --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From ca51362a1985f0cb958a957e2f17b73645981206 Mon Sep 17 00:00:00 2001 From: yacha Date: Thu, 9 Jul 2026 17:43:09 +0530 Subject: [PATCH 04/16] [ADD] estate: add menus and model field attributes Implemented Chapter 5 of the Odoo Server Framework tutorial. - Added window action for estate.property - Split XML into estate_property_views.xml and estate_menus.xml - Added three-level menu hierarchy - Added readonly and copy attributes - Added default values for bedrooms and availability date - Added reserved fields active and state --- estate/__manifest__.py | 6 ++++- estate/models/estate_property.py | 31 +++++++++++++++++++++----- estate/views/estate_menus.xml | 21 +++++++++++++++++ estate/views/estate_property_views.xml | 11 +++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ba0b3ed9c20..ad3ebaa49d5 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,5 +7,9 @@ "category": "Tutorials", "installable": True, "license": "LGPL-3", - "data": ["security/ir.model.access.csv"], + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", + ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 78eb2568d09..47fd06f797d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,17 +1,25 @@ -from odoo import models, fields +from odoo import api, models, fields +from dateutil.relativedelta import relativedelta + + +@api.model +def _default_date_availability(self): + return fields.Date.today() + relativedelta(months=3) class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(required=True) + active = fields.Boolean() + + name = fields.Char(required=True, default="Unknown") description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(copy=False, default=_default_date_availability) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -25,3 +33,16 @@ class EstateProperty(models.Model): ("west", "West"), ] ) + state = fields.Selection( + [ + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + string="State", + required=True, + copy=False, + default="new", + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..a91ca33bee7 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..00e611be6fe --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,11 @@ + + + + Properties + estate.property + list,form + + + \ No newline at end of file From 1187a721dfe1bf3af2ee684317249e339fa56db8 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Mon, 20 Jul 2026 18:51:23 +0530 Subject: [PATCH 05/16] [IMP] estate: add basic list and form views Introduce explicit list and form views for the estate.property model. The autogenerated views are sufficient for basic testing but do not provide a structured user interface. Adding custom XML views prepares the module for subsequent tutorial chapters, where the form and list views will be extended with notebooks, search views, widgets, and additional UI customizations. --- estate/views/estate_property_views.xml | 69 +++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 00e611be6fe..7290d58de62 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,72 @@ + + + estate.property.listview + estate.property + + + + + + + + + + + + + + + estate.property + +
+ + + +

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ @@ -8,4 +75,4 @@ list,form -
\ No newline at end of file + From e6442017c305c43752bd71ee09b29833fef7e717 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Tue, 21 Jul 2026 18:43:26 +0530 Subject: [PATCH 06/16] [IMP] estate: add active field to estate.property Add the active field to support record archiving. --- estate/models/estate_property.py | 2 +- estate/views/estate_property_views.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 47fd06f797d..ef4f6e38c86 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -11,7 +11,7 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - active = fields.Boolean() + active = fields.Boolean(default=True) name = fields.Char(required=True, default="Unknown") description = fields.Text() diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7290d58de62..2ae8fcd4cb6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -58,6 +58,7 @@ + From 33b894f2c71f7c798ef6829b7eac937f0762455b Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Mon, 27 Jul 2026 18:49:14 +0530 Subject: [PATCH 07/16] [IMP] estate: improve property search experience Help users find properties more efficiently by providing search shortcuts, a quick filter for available properties, and the ability to group records by postcode. This makes day-to-day property management faster as users can locate relevant records without manually browsing through the entire list. --- estate/views/estate_property_views.xml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2ae8fcd4cb6..1ab8c7574f7 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -68,6 +68,26 @@ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + From 70044716cb681aa98e8934169161ecb113039f1c Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Wed, 29 Jul 2026 18:45:27 +0530 Subject: [PATCH 08/16] [ADD] estate: improved property information management Add the ability to classify properties by their type and keep track of the buyer and the responsible salesperson. This makes property records more complete, easier to manage, and prepares the system for the complete property sales process. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 8 ++++++++ estate/models/estate_property_type.py | 8 ++++++++ estate/security/ir.model.access.csv | 3 ++- estate/views/estate_menus.xml | 10 ++++++++++ estate/views/estate_property_type_views.xml | 12 ++++++++++++ estate/views/estate_property_views.xml | 10 ++++++++++ 8 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ad3ebaa49d5..0109e8f4c8c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ "data": [ "security/ir.model.access.csv", "views/estate_property_views.xml", + "views/estate_property_type_views.xml", "views/estate_menus.xml", ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..40092a2d810 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,2 @@ from . import estate_property +from . import estate_property_type diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ef4f6e38c86..0dcd9af3bef 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -46,3 +46,11 @@ class EstateProperty(models.Model): 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) + + salesperson_id = fields.Many2one( + "res.users", string="Salesperson", default=lambda self: self.env.user + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..ad121b33163 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +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 0e11f47e58d..ac680bd78a6 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_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index a91ca33bee7..6c49ae29b97 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -15,6 +15,16 @@ + + + + + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..9a98e294c57 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,12 @@ + + + + + Property Types + estate.property.type + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1ab8c7574f7..bbd5b4459af 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -9,6 +9,7 @@ + @@ -36,6 +37,7 @@ + @@ -59,6 +61,13 @@ + + + + + + + @@ -74,6 +83,7 @@ + From b2f7e320aa3dc7337d36b40252c587f379661480 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Fri, 31 Jul 2026 18:47:10 +0530 Subject: [PATCH 09/16] [ADD] estate: enhance property classification Introduce property tags to classify and organize estate records using many-to-many relationships. This improves property categorization, enables flexible tagging of listings, and lays the foundation for advanced search, filtering, and future business features. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 2 ++ estate/models/estate_property_tag.py | 8 ++++++++ estate/security/ir.model.access.csv | 3 ++- estate/views/estate_menus.xml | 4 ++++ estate/views/estate_property_tag_views.xml | 12 ++++++++++++ estate/views/estate_property_views.xml | 2 ++ 8 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/views/estate_property_tag_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 0109e8f4c8c..9f7587741d9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,6 +11,7 @@ "security/ir.model.access.csv", "views/estate_property_views.xml", "views/estate_property_type_views.xml", + "views/estate_property_tag_views.xml", "views/estate_menus.xml", ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 40092a2d810..c620ac481a3 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,2 +1,3 @@ from . import estate_property from . import estate_property_type +from . import estate_property_tag diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0dcd9af3bef..e5150b2e931 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -54,3 +54,5 @@ class EstateProperty(models.Model): salesperson_id = fields.Many2one( "res.users", string="Salesperson", default=lambda self: self.env.user ) + + tag_ids = fields.Many2many("estate.property.tag", string="Tags") diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..3f730299abb --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real Estate Property Tag" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index ac680bd78a6..2f5005f9234 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,3 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 -access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 6c49ae29b97..e43031ee108 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -22,6 +22,10 @@ + + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..f2ba7700e7b --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,12 @@ + + + + + Property Tags + estate.property.tag + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index bbd5b4459af..32ad4fa9115 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -10,6 +10,7 @@ + @@ -38,6 +39,7 @@ + From e63dd8f3e6dcd3209a8bbedde15b223f75a5e739 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Fri, 7 Aug 2026 11:24:16 +0530 Subject: [PATCH 10/16] [ADD] estate: introduce property offer management Introduce a dedicated Property Offer model to record offers submitted by potential buyers for properties. Each offer stores the offered price, buyer, and current offer status, and is linked to a single property through relational fields. Display all related offers within the property form, enabling multiple offers to be managed from one place and preparing the estate module for the complete property selling workflow. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 2 ++ estate/models/estate_property_offer.py | 14 +++++++++ estate/security/ir.model.access.csv | 3 +- estate/views/estate_property_offer_views.xml | 32 ++++++++++++++++++++ estate/views/estate_property_views.xml | 7 +++-- 7 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/views/estate_property_offer_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9f7587741d9..f18861433ab 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,7 @@ "views/estate_property_views.xml", "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", + "views/estate_property_offer_views.xml", "views/estate_menus.xml", ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index c620ac481a3..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,3 +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 e5150b2e931..4874c49b24e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -56,3 +56,5 @@ class EstateProperty(models.Model): ) 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..bda51a9d147 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,14 @@ +from odoo import models, fields + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + + price = fields.Float() + status = fields.Selection( + [("accepted", "Accepted"), ("refused", "Refused")], copy=False + ) + + partner_id = fields.Many2one("res.partner", required=True) + property_id = fields.Many2one("estate.property", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 2f5005f9234..05bd9eefba4 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,4 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..29928dc0cea --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,32 @@ + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+ +
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 32ad4fa9115..2fab4845a11 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -10,7 +10,7 @@ - + @@ -66,12 +66,15 @@ - + + + + From 50dc46b3b36f6d5f7d21657729f965b9433286c9 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Thu, 13 Aug 2026 16:31:05 +0530 Subject: [PATCH 11/16] [ADD] estate: Runbot issue resolving --- estate/models/estate_property.py | 18 ++++++++++++++++++ estate/views/estate_property_views.xml | 8 ++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4874c49b24e..bfdba94762c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -24,6 +24,7 @@ class EstateProperty(models.Model): facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() + garden_area = fields.Integer() garden_orientation = fields.Selection( [ @@ -58,3 +59,20 @@ class EstateProperty(models.Model): tag_ids = fields.Many2many("estate.property.tag", string="Tags") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + + total_area = fields.Integer(compute="_compute_total") + + best_price = fields.Float(compute="_compute_best_price") + + @api.depends("living_area", "garden_area") + def _compute_total(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: + if record.offer_ids: + record.best_price = max(record.offer_ids.mapped("price")) + else: + record.best_price = 0 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2fab4845a11..4e1a7f6d61b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -60,8 +60,10 @@ - - + + + + @@ -93,6 +95,8 @@ + + From 29ff08612e5aa986ec2ee2d26813253cecd805f9 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Tue, 18 Aug 2026 12:52:54 +0530 Subject: [PATCH 12/16] [FIX] estate: address review comments Chapter 8: Address review feedback on the Real Estate tutorial implementation. Reformat the affected Python and XML files, fix import and attribute ordering, remove unnecessary field strings and XML declarations, and clean up the model and view definitions. These changes align the implementation with Odoo coding guidelines and improve the consistency and readability of the tutorial code. --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 25 ++++++++----------------- estate/views/estate_menus.xml | 9 --------- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f18861433ab..e930066f3d6 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,7 +1,7 @@ { "name": "Real Estate", "version": "1.0", - "author": "yacha", + "author": "Odoo S.A.", "depends": ["base"], "application": True, "category": "Tutorials", diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bfdba94762c..21259c259dd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,18 +1,17 @@ -from odoo import api, models, fields from dateutil.relativedelta import relativedelta - -@api.model -def _default_date_availability(self): - return fields.Date.today() + relativedelta(months=3) +from odoo import api, fields, models class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - active = fields.Boolean(default=True) + @api.model + def _default_date_availability(self): + return fields.Date.today() + relativedelta(months=3) + active = fields.Boolean(default=True) name = fields.Char(required=True, default="Unknown") description = fields.Text() postcode = fields.Char() @@ -24,7 +23,6 @@ class EstateProperty(models.Model): facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() garden_orientation = fields.Selection( [ @@ -47,25 +45,18 @@ class EstateProperty(models.Model): copy=False, default="new", ) - - property_type_id = fields.Many2one("estate.property.type", string="Property Type") - + property_type_id = fields.Many2one("estate.property.type") buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) - salesperson_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") - - total_area = fields.Integer(compute="_compute_total") - + total_area = fields.Integer(compute="_compute_total_area") best_price = fields.Float(compute="_compute_best_price") @api.depends("living_area", "garden_area") - def _compute_total(self): + def _compute_total_area(self): for record in self: record.total_area = record.living_area + record.garden_area diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index e43031ee108..542e842e879 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,32 +4,23 @@ - - - - - - - - - \ No newline at end of file From d7bfc0a09e26eed87c407a98ec3947dca5251706 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Tue, 18 Aug 2026 16:55:21 +0530 Subject: [PATCH 13/16] [IMP] estate: implemented computed fields and onchanges Chapter 8: Add computed fields for total area and best offer price. Implement offer deadline computation based on creation date and validity, including an inverse method to update validity when the deadline is changed. Add an onchange on the garden field to automatically set or clear the garden area and orientation values. Update the estate property and offer views to display the new fields. --- estate/models/estate_property.py | 9 ++++++++ estate/models/estate_property_offer.py | 24 +++++++++++++++++++- estate/views/estate_property_offer_views.xml | 4 ++++ estate/views/estate_property_views.xml | 4 ++-- 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 21259c259dd..4fb597fd40b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -67,3 +67,12 @@ def _compute_best_price(self): record.best_price = max(record.offer_ids.mapped("price")) else: record.best_price = 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 = "" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index bda51a9d147..90fe4ecf49c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import models, fields +from dateutil.relativedelta import relativedelta +from odoo import models, fields, api class EstatePropertyOffer(models.Model): @@ -12,3 +13,24 @@ class EstatePropertyOffer(models.Model): partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute="compute_date_deadline", inverse="inverse_date_deadline" + ) + + @api.depends("validity", "create_date") + def compute_date_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = record.create_date.date() + relativedelta( + days=record.validity + ) + else: + record.date_deadline = False + + def inverse_date_deadline(self): + for record in self: + if record.create_date and record.date_deadline: + record.validity = ( + record.date_deadline - record.create_date.date() + ).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 29928dc0cea..d6b57dd70a9 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + @@ -22,6 +24,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4e1a7f6d61b..55679f65bb6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -60,8 +60,8 @@ - - + + From e16025b8430ed2fbe36534624293cb0b062f2e68 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Mon, 24 Aug 2026 18:48:09 +0530 Subject: [PATCH 14/16] [ADD] travels: add TransientModel practical demo Add a standalone Yash Travels module to demonstrate the practical behavior of Odoo TransientModel. The module defines a transient travel model with list and form views, access rights, and menu/action configuration. It also allows testing transient record creation, database persistence, and automatic cleanup through the transient vacuum mechanism. --- travels/__init__.py | 1 + travels/__manifest__.py | 15 +++++++++++ travels/models/__init__.py | 1 + travels/models/travel.py | 11 ++++++++ travels/security/ir.model.access.csv | 2 ++ travels/views/travel_menus.xml | 10 ++++++++ travels/views/travel_views.xml | 38 ++++++++++++++++++++++++++++ 7 files changed, 78 insertions(+) create mode 100644 travels/__init__.py create mode 100644 travels/__manifest__.py create mode 100644 travels/models/__init__.py create mode 100644 travels/models/travel.py create mode 100644 travels/security/ir.model.access.csv create mode 100644 travels/views/travel_menus.xml create mode 100644 travels/views/travel_views.xml diff --git a/travels/__init__.py b/travels/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/travels/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/travels/__manifest__.py b/travels/__manifest__.py new file mode 100644 index 00000000000..4f5c7ce38a9 --- /dev/null +++ b/travels/__manifest__.py @@ -0,0 +1,15 @@ +{ + "name": "Yash Travels", + "version": "19.0.1.0.0", + "category": "Transient_demo", + "author": "ODOO S.A.", + "license": "LGPL-3", + "depends": ["base"], + "installable": True, + "application": True, + "data": [ + "security/ir.model.access.csv", + "views/travel_views.xml", + "views/travel_menus.xml", + ], +} diff --git a/travels/models/__init__.py b/travels/models/__init__.py new file mode 100644 index 00000000000..a05632b8908 --- /dev/null +++ b/travels/models/__init__.py @@ -0,0 +1 @@ +from . import travel diff --git a/travels/models/travel.py b/travels/models/travel.py new file mode 100644 index 00000000000..4082fe13267 --- /dev/null +++ b/travels/models/travel.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class Travel(models.TransientModel): + _name = "yash.travels" + _description = "Yash Travels" + # _transient_max_hours = 0.01 + _transient_max_count = 1 + name = fields.Char(required=True) + destination = fields.Char() + amount = fields.Float() diff --git a/travels/security/ir.model.access.csv b/travels/security/ir.model.access.csv new file mode 100644 index 00000000000..39ed537a713 --- /dev/null +++ b/travels/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_yash_travels,yash.travels access,model_yash_travels,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/travels/views/travel_menus.xml b/travels/views/travel_menus.xml new file mode 100644 index 00000000000..44c6a1c9f38 --- /dev/null +++ b/travels/views/travel_menus.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/travels/views/travel_views.xml b/travels/views/travel_views.xml new file mode 100644 index 00000000000..e791b6c239b --- /dev/null +++ b/travels/views/travel_views.xml @@ -0,0 +1,38 @@ + + + + + yash.travels.list + yash.travels + + + + + + + + + + + yash.travels.form + yash.travels + +
+ + + + + + + +
+
+
+ + + Yash Travels + yash.travels + list,form + + +
\ No newline at end of file From 668666db9d4acc6c73fdcbaf719cd037f5779488 Mon Sep 17 00:00:00 2001 From: yacha-odoo Date: Tue, 1 Sep 2026 18:47:45 +0530 Subject: [PATCH 15/16] [ADD] estate: implement property and offer action workflow Chapter - 9 - Added Sold and Cancel actions for properties with state validation. - Added Accept and Refuse actions for property offers. - Updated property buyer and selling price when an offer is accepted. - Added a confirmation wizard to handle offer acceptance. - Added Keep Previous and Accept New options for replacing an accepted offer. - Updated related views, menus, security access, and action configuration. --- estate/__init__.py | 1 + estate/__manifest__.py | 1 + estate/models/estate_property.py | 13 +++++++ estate/models/estate_property_offer.py | 27 +++++++++++++ estate/security/ir.model.access.csv | 3 +- estate/views/estate_menus.xml | 3 ++ estate/views/estate_property_offer_views.xml | 10 +++++ .../estate_property_offer_wizard_views.xml | 36 +++++++++++++++++ estate/views/estate_property_views.xml | 16 +++++++- estate/wizard/__init__.py | 1 + estate/wizard/estate_property_offer_wizard.py | 39 +++++++++++++++++++ 11 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 estate/views/estate_property_offer_wizard_views.xml create mode 100644 estate/wizard/__init__.py create mode 100644 estate/wizard/estate_property_offer_wizard.py diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..9b4296142f4 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ from . import models +from . import wizard diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e930066f3d6..8c50c0e0eb3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,6 +13,7 @@ "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", "views/estate_property_offer_views.xml", + "views/estate_property_offer_wizard_views.xml", "views/estate_menus.xml", ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4fb597fd40b..52c59294138 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -76,3 +77,15 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = "" + + def action_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("Cancelled Property cannot be Sold") + record.state = "sold" + + def action_cancel(self): + for record in self: + if record.state == "sold": + raise UserError("Sold Property cannot be Cancelled") + record.state = "cancelled" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 90fe4ecf49c..e9fceb35619 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -34,3 +34,30 @@ def inverse_date_deadline(self): record.validity = ( record.date_deadline - record.create_date.date() ).days + + def action_accept(self): + self.ensure_one() + accepted_offer = self.env["estate.property.offer"].search( + [ + ("property_id", "=", self.property_id.id), + ("status", "=", "accepted"), + ("id", "!=", self.id), + ], + limit=1, + ) + + if accepted_offer: + return { + "type": "ir.actions.act_window", + "name": "Accept Offer", + "res_model": "estate.property.offer.wizard", + "view_mode": "form", + "target": "new", + "context": {"default_offer_id": self.id}, + } + + def action_refuse(self): + for record in self: + record.status = "refused" + record.property_id.selling_price = 0.0 + record.property_id.buyer_id = None diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 05bd9eefba4..881b9afb8df 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_offer_wizard_user,access.estate.property.offer.wizard.user,model_estate_property_offer_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 542e842e879..af371203bae 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -20,6 +20,9 @@ + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index d6b57dd70a9..7451b6e00e1 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,6 +1,14 @@ + + Property Offer + estate.property.offer + list,form + + estate.property.offer.list estate.property.offer @@ -10,6 +18,7 @@ +
@@ -26,6 +35,7 @@ + diff --git a/estate/views/estate_property_offer_wizard_views.xml b/estate/views/estate_property_offer_wizard_views.xml new file mode 100644 index 00000000000..93c5b043fee --- /dev/null +++ b/estate/views/estate_property_offer_wizard_views.xml @@ -0,0 +1,36 @@ + + + + + + estate.property.offer.wizard.form + estate.property.offer.wizard + +
+ +
+ Another offer is already accepted for this property. + Do you want to replace it? +
+ +
+
+
+
+ +
+ +
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 55679f65bb6..4082a970712 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -27,6 +27,10 @@ estate.property
+
+
@@ -75,7 +79,17 @@ - + + + + + + +