From 0c47847a430d10c04272ed378e6f52f0cdbc8d63 Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Fri, 5 Jun 2026 14:12:29 +0530 Subject: [PATCH 01/12] [ADD] estate: initialize estate tutorial module create the directory structure and main manifest file for the estate real estate business module. This module will serve as the base for developing the property advertisement and offer management application. --- estate/__init__.py | 0 estate/__manifest__.py | 6 ++++++ 2 files changed, 6 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..8bc552b75df --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,6 @@ +{ + "name": "Real Estate", + "depends": ["base"], + "application" : True, + +} \ No newline at end of file From 37deca232a284f95fd96b88bd31fcc13043ba7c6 Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Mon, 8 Jun 2026 12:50:52 +0530 Subject: [PATCH 02/12] [IMP] estate: introduce the estate.property model Create the foundation of the 'estate' module (Real Estate application). Define the core model 'estate.property' to represent properties in the system. This model includes fields to track basic property information such as: - Name - Description - Postcode - Date of availability - Expected price - Selling price - Bedrooms, living area, facades, garage, and garden details --- estate/__init__.py | 1 + estate/__manifest__.py | 5 ++--- estate/models/__init__.py | 1 + estate/models/estate_property.py | 23 +++++++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) 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 index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 8bc552b75df..d36fb1c0983 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,6 +1,5 @@ { "name": "Real Estate", "depends": ["base"], - "application" : True, - -} \ No newline at end of file + "application": True, +} 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..f4cdc524147 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,23 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(string="Title", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + date_availability = fields.Date(string="Available From") + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bedrooms") + living_area = fields.Integer(string="Living Area (sqm)") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area (sqm)") + garden_orientation = fields.Selection( + [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], + string="Garden Orientation", + ) From 90cb78407ac650218fe5127bbfcc7e3904183b7c Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Tue, 9 Jun 2026 16:10:05 +0530 Subject: [PATCH 03/12] [ADD] estate: add access rights for estate.property model - Create `ir.model.access.csv` to define access control rules. - Grant full CRUD (read, write, create, delete) permissions to the base user group (`base.group_user`). - Register the security CSV file inside the manifest `__manifest__.py`. --- estate/__manifest__.py | 6 ++++++ estate/security/ir.model.access.csv | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d36fb1c0983..04c285bde27 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,11 @@ { "name": "Real Estate", "depends": ["base"], + "data" : [ + 'security/ir.model.access.csv', + ], "application": True, + "author" : "dhruvi kalariya", + "license" : "LGPL-3", + "installable" : True, } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..73a8b2a8ba7 --- /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_access,model_estate_property,base.group_user,1,1,1,1 From bbaec4ebe8450384cbc0ea85257215e25fc67360 Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Thu, 11 Jun 2026 18:28:43 +0530 Subject: [PATCH 04/12] [ADD] estate: ui views, menus, actions and field attributes - Add window action for estate.property model - Add 3-level menu hierarchy (Root, Advertisements) with web_icon - Set selling_price as read-only and non-copyable - Set default values for bedrooms (2) and date_availability (3 months from today) - Add active and state reserved fields with default values --- estate/__manifest__.py | 12 ++++++---- estate/models/estate_property.py | 29 +++++++++++++++++++---- estate/static/description/icon.png | Bin 0 -> 8240 bytes estate/views/estate_menus.xml | 31 +++++++++++++++++++++++++ estate/views/estate_property_views.xml | 9 +++++++ 5 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 estate/static/description/icon.png 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 04c285bde27..15f8e200187 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,11 +1,13 @@ { "name": "Real Estate", "depends": ["base"], - "data" : [ - 'security/ir.model.access.csv', + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", ], "application": True, - "author" : "dhruvi kalariya", - "license" : "LGPL-3", - "installable" : True, + "author": "dhruvi kalariya", + "license": "LGPL-3", + "installable": True, } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f4cdc524147..ebafa73e5fa 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,17 +1,24 @@ from odoo import fields, models +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(string="Title", required=True) + name = fields.Char(string="Title", required=True, default="Unknown") description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Available From") - expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price") - bedrooms = fields.Integer(string="Bedrooms") + date_availability = fields.Date( + string="Available From", + copy=False, + default=lambda self: fields.Date.context_today(self) + relativedelta(months=3), + ) + expected_price = fields.Float( + string="Expected Price", required=True, digits=(16, 4) + ) + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer(string="Bedrooms", default=2) living_area = fields.Integer(string="Living Area (sqm)") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") @@ -21,3 +28,15 @@ class EstateProperty(models.Model): [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], string="Garden Orientation", ) + active = fields.Boolean(string="Active", default=True) + state = fields.Selection( + [ + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + copy=False, + default="new", + ) diff --git a/estate/static/description/icon.png b/estate/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..49aa15839db6d2e844b885766df54b9ee128e1f3 GIT binary patch literal 8240 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 X;A!7?anA7VKEVBEf28b?_wWA&&u8eI literal 0 HcmV?d00001 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..ef769c396f2 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..b8fd94b7a7b --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,9 @@ + + + + Properties + estate.property + list,form,kanban + + + From f8d62677b5109d34f8dea01b597990069b7d2aa2 Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Tue, 16 Jun 2026 10:13:43 +0530 Subject: [PATCH 05/12] [ADD] estate: add views and security groups - Create security/security_groups.xml to define the Real Estate category, Access privilege, and user groups (Agent, Buyer, Owner) - Add list, form, and search views for the estate.property model in views/estate_property_views.xml --- estate/security/security_groups.xml | 30 +++++++++++ estate/views/estate_property_views.xml | 75 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 estate/security/security_groups.xml diff --git a/estate/security/security_groups.xml b/estate/security/security_groups.xml new file mode 100644 index 00000000000..5cd2216d79a --- /dev/null +++ b/estate/security/security_groups.xml @@ -0,0 +1,30 @@ + + + + Real Estate + + + + Access + + + + + Agent + + + + + + Buyer + + + + + + Owner + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index b8fd94b7a7b..f69ecea57bf 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,4 +6,79 @@ list,form,kanban + + estate.property.list + estate.property + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + From 4bd6c021cad199e1fe9b9a5f7384551a17f901a2 Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Fri, 19 Jun 2026 11:13:25 +0530 Subject: [PATCH 06/12] [IMP] estate: add new features and refactor code to match guidelines Introduce property types, tags, and offers to expand module capabilities, while refactoring existing code to comply with Odoo standards. - Add new models and views for property types, tags, and offers. - Rename Many2one fields 'buyer' and 'salesperson' to 'buyer_id' and 'salesperson_id' in 'estate.property'. - Rename XML view and action record IDs to use '[model_name]_view_[type]' and '[model_name]_action' naming patterns. - Move security configurations to 'security/estate_security.xml'. - Align Python imports and XML files formatting. --- estate/__manifest__.py | 4 ++ estate/models/__init__.py | 3 + estate/models/estate_property.py | 20 ++++++- estate/models/estate_property_offer.py | 18 ++++++ estate/models/estate_property_tag.py | 8 +++ estate/models/estate_property_type.py | 8 +++ ...ecurity_groups.xml => estate_security.xml} | 10 ++-- estate/security/ir.model.access.csv | 18 ++++++ estate/views/estate_menus.xml | 55 ++++++++++--------- estate/views/estate_property_offer_views.xml | 30 ++++++++++ estate/views/estate_property_tag_views.xml | 8 +++ estate/views/estate_property_type_views.xml | 8 +++ estate/views/estate_property_views.xml | 26 ++++++--- 13 files changed, 176 insertions(+), 40 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 rename estate/security/{security_groups.xml => estate_security.xml} (73%) 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 15f8e200187..bfe686cc3f9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,8 +2,12 @@ "name": "Real Estate", "depends": ["base"], "data": [ + "security/estate_security.xml", "security/ir.model.access.csv", "views/estate_property_views.xml", + "views/estate_property_type_views.xml", + "views/estate_property_tag_views.xml", + "views/estate_property_offer_views.xml", "views/estate_menus.xml", ], "application": True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ebafa73e5fa..42db6d6fc84 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,12 +1,15 @@ -from odoo import fields, models from dateutil.relativedelta import relativedelta +from odoo import fields, models + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" name = fields.Char(string="Title", required=True, default="Unknown") + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + tag_ids = fields.Many2many("estate.property.tag", string="Tags") description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") date_availability = fields.Date( @@ -25,7 +28,12 @@ class EstateProperty(models.Model): garden = fields.Boolean(string="Garden") garden_area = fields.Integer(string="Garden Area (sqm)") garden_orientation = fields.Selection( - [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], + [ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], string="Garden Orientation", ) active = fields.Boolean(string="Active", default=True) @@ -37,6 +45,14 @@ class EstateProperty(models.Model): ("sold", "Sold"), ("cancelled", "Cancelled"), ], + string="Status", copy=False, default="new", ) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + salesperson_id = fields.Many2one( + "res.users", + string="Salesperson", + default=lambda self: self.env.user, + ) + 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..e238ea5e280 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,18 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + price = fields.Float(string="Price") + status = fields.Selection( + [ + ("accepted", "Accepted"), + ("rejected", "Rejected"), + ], + string="Status", + copy=False, + ) + partner_id = fields.Many2one("res.partner", string="Partner", 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..2c62c64ee93 --- /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(string="Name", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..643c791ed2e --- /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(string="Type", required=True) \ No newline at end of file diff --git a/estate/security/security_groups.xml b/estate/security/estate_security.xml similarity index 73% rename from estate/security/security_groups.xml rename to estate/security/estate_security.xml index 5cd2216d79a..bcfbd7bb73b 100644 --- a/estate/security/security_groups.xml +++ b/estate/security/estate_security.xml @@ -4,25 +4,23 @@ Real Estate - + Access - - + Agent - + Buyer - + Owner diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 73a8b2a8ba7..5800cb94f40 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,20 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,estate_property_access,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_agent,estate_property_agent,model_estate_property,estate.res_groups_agent,1,1,1,1 +access_estate_property_buyer,estate_property_buyer,model_estate_property,estate.res_groups_buyer,1,0,0,0 +access_estate_property_owner,estate_property_owner,model_estate_property,estate.res_groups_owner,1,1,1,1 + +access_estate_property_type,estate_property_type_access,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_type_agent,estate_property_type_agent,model_estate_property_type,estate.res_groups_agent,1,1,1,1 +access_estate_property_type_buyer,estate_property_type_buyer,model_estate_property_type,estate.res_groups_buyer,1,0,0,0 +access_estate_property_type_owner,estate_property_type_owner,model_estate_property_type,estate.res_groups_owner,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_tag_agent,estate_property_tag_agent,model_estate_property_tag,estate.res_groups_agent,1,1,1,1 +access_estate_property_tag_buyer,estate_property_tag_buyer,model_estate_property_tag,estate.res_groups_buyer,1,0,0,0 +access_estate_property_tag_owner,estate_property_tag_owner,model_estate_property_tag,estate.res_groups_owner,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_agent,estate_property_offer_agent,model_estate_property_offer,estate.res_groups_agent,1,1,1,1 +access_estate_property_offer_buyer,estate_property_offer_buyer,model_estate_property_offer,estate.res_groups_buyer,1,0,0,0 +access_estate_property_offer_owner,estate_property_offer_owner,model_estate_property_offer,estate.res_groups_owner,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index ef769c396f2..6929b0344ec 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,31 +1,36 @@ - + - - - - - - - - + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..8b2af3d04eb --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,30 @@ + + + + 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..0ec3dc550e0 --- /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..6384061b8ee --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,8 @@ + + + + Property Types + estate.property.type + list,form + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f69ecea57bf..6b9ab3631fc 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,4 +1,4 @@ - + Properties @@ -6,12 +6,13 @@ list,form,kanban - + estate.property.list estate.property + @@ -22,8 +23,7 @@ - - + estate.property.form estate.property @@ -34,6 +34,8 @@ + + @@ -57,13 +59,22 @@ + + + + + + + + + - - - + + + estate.property.search estate.property @@ -77,6 +88,7 @@ + From 0b9b3b89b56025676e43fff83c796282e51581b5 Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Mon, 22 Jun 2026 17:37:36 +0530 Subject: [PATCH 07/12] [IMP] estate: add demo data and improve property views - Add demo data files for property types (demo_types.xml), property tags (demo_tags.xml), and properties (demo_property.xml). - Add tag_ids field with widget 'many2many_tags' to property list view. - Add 'Has garden' and 'Has garage' filters to the property search view. --- estate/__manifest__.py | 3 ++ estate/data/demo_property.xml | 38 ++++++++++++++++++++++++++ estate/data/demo_tags.xml | 14 ++++++++++ estate/data/demo_types.xml | 14 ++++++++++ estate/models/estate_property_offer.py | 2 +- estate/models/estate_property_type.py | 3 +- estate/views/estate_property_views.xml | 4 +++ 7 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 estate/data/demo_property.xml create mode 100644 estate/data/demo_tags.xml create mode 100644 estate/data/demo_types.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index bfe686cc3f9..81784e94540 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,6 +9,9 @@ "views/estate_property_tag_views.xml", "views/estate_property_offer_views.xml", "views/estate_menus.xml", + "data/demo_types.xml", + "data/demo_tags.xml", + "data/demo_property.xml", ], "application": True, "author": "dhruvi kalariya", diff --git a/estate/data/demo_property.xml b/estate/data/demo_property.xml new file mode 100644 index 00000000000..ce115dce60e --- /dev/null +++ b/estate/data/demo_property.xml @@ -0,0 +1,38 @@ + + + + House in ahemedabad + 700000 + near sg highway + 123456 + 123456 + 3 + 180 + 2 + True + True + 120 + south + True + + + + + + Flat in surat + 500000 + near station + 543210 + 123456 + 3 + 180 + 2 + True + True + 120 + west + True + + + + diff --git a/estate/data/demo_tags.xml b/estate/data/demo_tags.xml new file mode 100644 index 00000000000..fd46541c1e0 --- /dev/null +++ b/estate/data/demo_tags.xml @@ -0,0 +1,14 @@ + + + + Renovated + + + + Cozy + + + + Luxury + + diff --git a/estate/data/demo_types.xml b/estate/data/demo_types.xml new file mode 100644 index 00000000000..879d4a4e2f0 --- /dev/null +++ b/estate/data/demo_types.xml @@ -0,0 +1,14 @@ + + + + House + + + + Apartment + + + + villa + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e238ea5e280..af8929d2c84 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -15,4 +15,4 @@ class EstatePropertyOffer(models.Model): copy=False, ) partner_id = fields.Many2one("res.partner", string="Partner", required=True) - property_id = fields.Many2one("estate.property", string="Property", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True, ondelete="cascade") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 643c791ed2e..1a97c3f4a96 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -5,4 +5,5 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "Real Estate Property Type" - name = fields.Char(string="Type", required=True) \ No newline at end of file + name = fields.Char(string="Type", required=True) + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 6b9ab3631fc..2f3a03db7ce 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -14,6 +14,7 @@ + @@ -86,6 +87,9 @@ + + + From d977747b77b79a49865049d47e37999f4037037a Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Mon, 29 Jun 2026 10:58:27 +0530 Subject: [PATCH 08/12] [IMP] estate: implement computed fields, onchanges, and kanban view - Added `total_area` and `best_price` computed fields in `estate.property`. - Implemented `onchange` for the `garden` field to auto-fill fields. - Added `validity` and `date_deadline` (computed with inverse method) in `estate.property.offer`. - Updated `estate.property.offer` views to include new validity and deadline fields. - Updated `estate.property` form view to display `best_price` and `total_area`. - Added a Kanban view for `estate.property`. --- estate/models/estate_property.py | 28 ++++++++++++++- estate/models/estate_property_offer.py | 36 ++++++++++++++++++-- estate/models/estate_property_type.py | 1 - estate/views/estate_property_offer_views.xml | 4 +++ estate/views/estate_property_views.xml | 34 ++++++++++++++++++ 5 files changed, 99 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 42db6d6fc84..2c7d86ecb00 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,6 @@ from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -56,3 +56,29 @@ class EstateProperty(models.Model): default=lambda self: self.env.user, ) offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + total_area = fields.Integer( + string="Total Area (sqm)", compute="_compute_total_area" + ) + best_price = fields.Float(string="Best Price", 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 + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + record.best_price = ( + max(record.offer_ids.mapped("price")) if record.offer_ids else 0.0 + ) + + @api.onchange("garden") + def _onchange_garden(self): + for record in self: + if record.garden: + record.garden_area = 10 + record.garden_orientation = "north" + else: + record.garden_area = 0 + record.garden_orientation = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index af8929d2c84..5e7962a8596 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from datetime import timedelta + +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -15,4 +17,34 @@ class EstatePropertyOffer(models.Model): copy=False, ) partner_id = fields.Many2one("res.partner", string="Partner", required=True) - property_id = fields.Many2one("estate.property", string="Property", required=True, ondelete="cascade") + property_id = fields.Many2one( + "estate.property", string="Property", required=True, ondelete="cascade" + ) + validity = fields.Integer(string="Validity", default=7) + date_deadline = fields.Date( + string="Deadline", + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + ) + + @api.depends("create_date", "validity") + def _compute_date_deadline(self): + for offer in self: + start_date = ( + offer.create_date.date() if offer.create_date else fields.Date.today() + ) + offer.date_deadline = start_date + timedelta(days=offer.validity) + + def _inverse_date_deadline(self): + for offer in self: + if offer.date_deadline: + start_date = ( + offer.create_date.date() + if offer.create_date + else fields.Date.today() + ) + offer.validity = (offer.date_deadline - start_date).days + + @api.onchange("date_deadline") + def _onchange_date_deadline(self): + self._inverse_date_deadline() diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 1a97c3f4a96..41dfae15fda 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,4 +6,3 @@ class EstatePropertyType(models.Model): _description = "Real Estate Property Type" name = fields.Char(string="Type", required=True) - \ 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 8b2af3d04eb..95c32aac850 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,8 @@ + + @@ -21,6 +23,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2f3a03db7ce..4fa473bb371 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -42,6 +42,7 @@ + @@ -56,6 +57,7 @@ + @@ -75,6 +77,38 @@ + + estate.property.kanban + estate.property + + + + + + +
+ + + +
+
+ Expected Price: +
+
+ Best Offer: +
+
+ Selling Price: +
+
+ +
+
+
+
+
+
+ estate.property.search estate.property From d31934ba233d8820cda74aa30ff7463e6a7200bd Mon Sep 17 00:00:00 2001 From: dhruvikalariyaa Date: Tue, 30 Jun 2026 10:53:30 +0530 Subject: [PATCH 09/12] [IMP] estate: add property actions and SQL constraints - Add business logic for managing property and offer states (Accept, Reject, Sold, Cancel). - Introduce SQL constraints to ensure positive values for expected price, selling price, and offer price, along with unique constraints for tags and types. - Enhance UI with tag color pickers and inline editable lists. --- estate/data/demo_property.xml | 4 +-- estate/data/demo_tags.xml | 14 ++++++-- estate/data/demo_types.xml | 6 ++-- estate/models/estate_property.py | 34 ++++++++++++++++---- estate/models/estate_property_offer.py | 17 ++++++++++ estate/models/estate_property_tag.py | 12 ++++++- estate/models/estate_property_type.py | 4 ++- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_tag_views.xml | 26 +++++++++++++++ estate/views/estate_property_type_views.xml | 10 ++++++ estate/views/estate_property_views.xml | 7 +++- 11 files changed, 119 insertions(+), 17 deletions(-) diff --git a/estate/data/demo_property.xml b/estate/data/demo_property.xml index ce115dce60e..b4196de22df 100644 --- a/estate/data/demo_property.xml +++ b/estate/data/demo_property.xml @@ -15,7 +15,7 @@ south True - + @@ -33,6 +33,6 @@ west True - +
diff --git a/estate/data/demo_tags.xml b/estate/data/demo_tags.xml index fd46541c1e0..48db146711d 100644 --- a/estate/data/demo_tags.xml +++ b/estate/data/demo_tags.xml @@ -1,14 +1,22 @@ - + Renovated - + Cozy - + Luxury + + + With Pool + + + + Sea View + diff --git a/estate/data/demo_types.xml b/estate/data/demo_types.xml index 879d4a4e2f0..f8f0793baf6 100644 --- a/estate/data/demo_types.xml +++ b/estate/data/demo_types.xml @@ -1,14 +1,14 @@ - House + Residential - Apartment + Commercial - villa + Industrial diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2c7d86ecb00..2bfc8c7bbff 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): @@ -61,6 +62,14 @@ class EstateProperty(models.Model): ) best_price = fields.Float(string="Best Price", compute="_compute_best_price") + _check_expected_price = models.Constraint( + "CHECK(expected_price > 0)", + "A property expected price must be strictly positive.", + ) + _check_selling_price = models.Constraint( + "CHECK(selling_price >= 0)", "A property selling price must be positive." + ) + @api.depends("living_area", "garden_area") def _compute_total_area(self): for record in self: @@ -75,10 +84,23 @@ def _compute_best_price(self): @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_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("You can not sold a cancelled property.") + record.state = "sold" + return True + + def action_cancel(self): for record in self: - if record.garden: - record.garden_area = 10 - record.garden_orientation = "north" - else: - record.garden_area = 0 - record.garden_orientation = False + if record.state == "sold": + raise UserError("You can not cancel a sold property.") + record.state = "cancelled" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5e7962a8596..4f66728c150 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -8,6 +8,10 @@ class EstatePropertyOffer(models.Model): _description = "Real Estate Property Offer" price = fields.Float(string="Price") + + _check_price = models.Constraint( + "CHECK(price > 0)", "An offer price must be strictly positive." + ) status = fields.Selection( [ ("accepted", "Accepted"), @@ -48,3 +52,16 @@ def _inverse_date_deadline(self): @api.onchange("date_deadline") def _onchange_date_deadline(self): self._inverse_date_deadline() + + def action_accept(self): + for offer in self: + offer.status = "accepted" + offer.property_id.selling_price = offer.price + offer.property_id.buyer_id = offer.partner_id + offer.property_id.state = "offer_accepted" + return True + + def action_reject(self): + for offer in self: + offer.status = "rejected" + return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 2c62c64ee93..813cb80c82f 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,8 +1,18 @@ -from odoo import fields, models +from random import randint + +from odoo import api, fields, models class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "Real Estate Property Tag" + def _default_color(self): + return randint(1, 11) + name = fields.Char(string="Name", required=True) + color = fields.Integer( + string="Color Index", default=lambda self: self._default_color() + ) + + _check_name = models.Constraint("UNIQUE(name)", "Tag name must be unique") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 41dfae15fda..695d5b977fd 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyType(models.Model): @@ -6,3 +6,5 @@ class EstatePropertyType(models.Model): _description = "Real Estate Property Type" name = fields.Char(string="Type", required=True) + + _check_name = models.Constraint("UNIQUE(name)", "Type name must be unique") diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 95c32aac850..24a6a5da355 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,6 +9,8 @@ +