From a321dfa440f111bb23fda6ae4a5576249ef01624 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Thu, 5 Feb 2026 17:56:29 +0530 Subject: [PATCH 01/16] [ADD] estate: implement estate advertisement module. - Created the core estate model and initialized database schema. - Configured manifest with required metadata and dependencies. - Handled code cleanup, including license headers and trailing spaces. - Imported models into the root directory for proper initialization. --- estate/__init__.py | 2 ++ estate/__manifest__.py | 15 +++++++++++++++ estate/models/__init__.py | 1 + 3 files changed, 18 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..8f5ced4b094 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1,2 @@ +# import the model directory +from . import models # noqa: F401 diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..b39e219629a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,15 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + "name": "Real Estate Advertisement ", + "version": "1.0", + "depends": ["base"], + "website": "https://www.odoo.com/app/estate", + "summary": "This module is for Real estate advertisement.", + "category": "estate", + "data": [], + "installable": True, + "application": True, + "author": "odoo-pupat", + "licence": "LGPL-3", +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..9d5e62fe812 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property # noqa: F401 From 9e1664e96dc8803d332ecb6f8212b9e125c11fbf Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Fri, 6 Feb 2026 17:48:28 +0530 Subject: [PATCH 02/16] [ADD] estate: estate_property table created. - It covers chapter-3 task - added the required fields. --- estate/models/estate_property.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 estate/models/estate_property.py diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..d59021ec662 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "estate property definition" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + data_availability = fields.Date() + expected_salary = 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(required=True) + garden_orientation = fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], + ) From 0568700ed34f508141d5a366e45a486caed32d49 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Sun, 8 Feb 2026 15:19:40 +0530 Subject: [PATCH 03/16] [IMP] estate: define the csv file used in security directory. - Defined access control CSV in the security directory. - Configured permissions for estate property records. --- estate/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b39e219629a..0b1039e7125 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,7 +7,7 @@ "website": "https://www.odoo.com/app/estate", "summary": "This module is for Real estate advertisement.", "category": "estate", - "data": [], + "data": ["security/ir.model.access.csv"], "installable": True, "application": True, "author": "odoo-pupat", From 935812585e4a965b3369d47970d4e90462d01891 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Sun, 8 Feb 2026 15:28:34 +0530 Subject: [PATCH 04/16] [ADD] estate: secuity policy is added this covers access right properties. - Added access rights for estate.property model. - Ensured proper read/write/create/delete permissions. --- estate/__manifest__.py | 2 +- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 0b1039e7125..a9611333b0d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,4 +1,4 @@ -# Part of Odoo. See LICENSE file for full copyright and licensing details. + { "name": "Real Estate Advertisement ", diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..98f4671fb0d --- /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 +estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 From 2d38553c675d9c08ef34f5c5603c7850b7144654 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Mon, 9 Feb 2026 10:54:14 +0530 Subject: [PATCH 05/16] [IMP] estate: estate_property table's attributes are added with some constraints. - This file includes the model's description. - Included model description for clarity. --- estate/__manifest__.py | 10 +++--- estate/models/estate_property.py | 44 +++++++++++++++++++------- estate/views/estate_menus.xml | 8 +++++ estate/views/estate_property_views.xml | 9 ++++++ 4 files changed, 56 insertions(+), 15 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 a9611333b0d..d07e5c70be0 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,3 @@ - - { "name": "Real Estate Advertisement ", "version": "1.0", @@ -7,9 +5,13 @@ "website": "https://www.odoo.com/app/estate", "summary": "This module is for Real estate advertisement.", "category": "estate", - "data": ["security/ir.model.access.csv"], + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", + ], "installable": True, "application": True, "author": "odoo-pupat", - "licence": "LGPL-3", + "license": "LGPL-3", } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d59021ec662..f51f2803243 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,3 +1,4 @@ +from dateutil.relativedelta import relativedelta from odoo import fields, models @@ -8,20 +9,41 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - data_availability = fields.Date() + date_availability = fields.Date( + default=lambda self: fields.Date.context_today(self) + relativedelta(months=3), + copy=False, + ) expected_salary = 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() garden = fields.Boolean() - garden_area = fields.Integer(required=True) - garden_orientation = fields.Selection( - selection=[ - ("north", "North"), - ("south", "South"), - ("east", "East"), - ("west", "West"), - ], + garden_area = fields.Integer() + garden_orientation = ( + fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] + ), + ) + + active = fields.Boolean(default=True) + State = ( + fields.Selection( + Status=[ + ("new", "New"), + ("offer received", "Offer Recieved"), + ("offer accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + required=True, + default="New", + copy=False, + ), ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..cc5e0f237f2 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..42cd4cb4c97 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,9 @@ + + + + + Estate Property + estate.property + list,form + + From 30e0df5993ce9ad2f648d1129bdb2870987106d3 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Fri, 13 Feb 2026 10:32:45 +0530 Subject: [PATCH 06/16] [ADD] estate: add custom list, form and search views - Added list view to display records in tabular format. - Added form view with grouped fields. - Added search view with filter for 'Available' properties and group by postcode. --- estate/models/estate_property.py | 67 +++++++++++------------ estate/views/estate_property_views.xml | 74 ++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 35 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f51f2803243..ff70364e1da 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,3 @@ -from dateutil.relativedelta import relativedelta from odoo import fields, models @@ -6,44 +5,42 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "estate property definition" - name = fields.Char(required=True) - description = fields.Text() - postcode = fields.Char() + name = fields.Char(string="Property Name", required=True) + description = fields.Text(string="Description", required=True) + postcode = fields.Char(string="Postcode") date_availability = fields.Date( - default=lambda self: fields.Date.context_today(self) + relativedelta(months=3), + string="Available From", + default=lambda self: fields.Date.add(fields.Date.today(), months=3), copy=False, ) - expected_salary = 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() - garden_orientation = ( - fields.Selection( - selection=[ - ("north", "North"), - ("south", "South"), - ("east", "East"), - ("west", "West"), - ] - ), + expected_price = fields.Float(string="Expected Price", required=True) + 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") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area (sqm)") + garden_orientation = fields.Selection( + string="Garden Orientation", + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], ) active = fields.Boolean(default=True) - State = ( - fields.Selection( - Status=[ - ("new", "New"), - ("offer received", "Offer Recieved"), - ("offer accepted", "Offer Accepted"), - ("sold", "Sold"), - ("cancelled", "Cancelled"), - ], - required=True, - default="New", - copy=False, - ), + state = fields.Selection( + selection=[ + ("new", "New"), + ("offer_received", "Offer Recieved"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + required=True, + default="new", + copy=False, ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 42cd4cb4c97..b5f420ea1d8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,9 +1,83 @@ + Estate Property estate.property list,form + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + + + estate_property_search + estate.property + + + + + + + + + + + + + + + + + estate.property.view.form + estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
From f2d7658e55b0fa72d281b732331c298dbce74971 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Mon, 16 Feb 2026 12:19:23 +0530 Subject: [PATCH 07/16] [ADD] estate: added relationship between models, covers(ch-7) - used many2one relation in estate_property_type model, signifies a property has individual type but may assigned to multiple other properties - used Many2many relationship in estate_property_tag model,signifies a property has multiple tag but one tag can be assigned to multiple properties. - used One2many relationship in estate_property_offer model, signifies a property has multiple offer but each offers belongs to one property. --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 7 ++- estate/models/estate_property.py | 9 ++++ estate/models/estate_property_offer.py | 17 ++++++++ estate/models/estate_property_tag.py | 9 ++++ estate/models/estate_property_type.py | 9 ++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 40 ++++++++++++++--- estate/views/estate_property_offers_views.xml | 43 +++++++++++++++++++ estate/views/estate_property_tag_views.xml | 39 +++++++++++++++++ estate/views/estate_property_type_views.xml | 39 +++++++++++++++++ estate/views/estate_property_views.xml | 40 +++++++++++++++-- 12 files changed, 248 insertions(+), 10 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_offers_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 d07e5c70be0..d84b592558d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,9 @@ "data": [ "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_offers_views.xml", "views/estate_menus.xml", ], "installable": True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 9d5e62fe812..f2c29d4a968 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,6 @@ -from . import estate_property # noqa: F401 +from . import ( + estate_property, # noqa: F401 + estate_property_offer, # noqa: F401 + estate_property_tag, # noqa: F401 + estate_property_type, # noqa: F401 +) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ff70364e1da..800e0e42cf7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -44,3 +44,12 @@ class EstateProperty(models.Model): default="new", copy=False, ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + salesman_id = fields.Many2one( + "res.users", + string=" Salesman", + default=lambda self: self.env.user, + ) + buyer_id = fields.Many2one("res.partner", string=" Buyer") + property_tag_ids = fields.Many2many("estate.property.tag") + property_offer_ids = fields.One2many("estate.property.offer", "salesman_id") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..866e4a35d40 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,17 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer description" + + price = fields.Float(string="Price") + property_offer_ids = fields.Integer(Sting="Offer") + state = fields.Selection( + string="Status", + copy=False, + selection=[("acepted", "Accepted"), ("refused", "Refused")], + ) + + salesman_id = fields.Many2one("res.partner", required=True, string="Partner") + property_id = fields.Many2one("estate.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..4f4261728b9 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "estate property tag" + + name = fields.Char(required=True) + property_tag_ids = fields.Integer() diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..48179434328 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "estate property type" + + name = fields.Char(string="Property Type", required=True) + property_type_id = fields.Integer() diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 98f4671fb0d..0c0b62b7fee 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 +estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 +estate.access_estate_property_offer,access_estate_property_offer,estate.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 cc5e0f237f2..6f3562593c4 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,8 +1,38 @@ + + - - - - - + + + Property Tags + estate.property.tag + list,form + + + + + + + + + + + + + + + diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml new file mode 100644 index 00000000000..c041521ee56 --- /dev/null +++ b/estate/views/estate_property_offers_views.xml @@ -0,0 +1,43 @@ + + + + + + Estate Property offer + estate.property.offer + list,form + + + + 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..723557eb983 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,39 @@ + + + + + + Estate Property Tag + 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..6a25296aaf3 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,39 @@ + + + + + + Estate Property Type + 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 b5f420ea1d8..9c72800c1aa 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -13,7 +13,9 @@ estate.property - + + + @@ -25,14 +27,16 @@ - + property_tag_id = fields.Many2many("res.partner") - + estate_property_search estate.property - + + + @@ -51,8 +55,17 @@
+
+

+ + +

+
+ + + @@ -74,6 +87,25 @@ + + + + + + + + + + + + + + + + + + +
From 615ccd3da39369fed978a04dbfb787f818e2d622 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Mon, 16 Feb 2026 12:58:48 +0530 Subject: [PATCH 08/16] [FIX] estate: fixed the warning related to naming convention, covers(ch-7) - corrected the string datatype misspelling -Changed the same name conflict between 'name' and 'property_type_id'. --- estate/models/estate_property_offer.py | 2 +- estate/models/estate_property_type.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 866e4a35d40..bb9688281fd 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -6,7 +6,7 @@ class EstatePropertyOffer(models.Model): _description = "Estate Property Offer description" price = fields.Float(string="Price") - property_offer_ids = fields.Integer(Sting="Offer") + property_offer_ids = fields.Integer(string="Offer") state = fields.Selection( string="Status", copy=False, diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 48179434328..ce96136373e 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -5,5 +5,5 @@ class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "estate property type" - name = fields.Char(string="Property Type", required=True) + name = fields.Char(string="Property Category", required=True) property_type_id = fields.Integer() From ce34b600b842e372e4eb5c398fd0ad0798f26077 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Tue, 17 Feb 2026 12:06:59 +0530 Subject: [PATCH 09/16] [ADD] estate: added computed fields, and onchange method decorator,cover(ch-8) - used compute method to specify the relationship between fields. - Inverse function allows to edit the read-only computed fields. - Onchange method decorator dynamically changes other dependent field values. This chapter covers the implementation of computed fields and Onchange decorator which allows to perform computations and defines the link between the fields. --- estate/models/estate_property.py | 34 ++++++++++++++++++- estate/models/estate_property_offer.py | 17 +++++++++- estate/views/estate_property_offers_views.xml | 2 ++ estate/views/estate_property_views.xml | 7 ++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 800e0e42cf7..899f3d5adb7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -8,6 +8,8 @@ class EstateProperty(models.Model): name = fields.Char(string="Property Name", required=True) description = fields.Text(string="Description", required=True) postcode = fields.Char(string="Postcode") + validity = fields.Integer(default=7) + date_deadline = fields.Date() date_availability = fields.Date( string="Available From", default=lambda self: fields.Date.add(fields.Date.today(), months=3), @@ -44,6 +46,7 @@ class EstateProperty(models.Model): default="new", copy=False, ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") salesman_id = fields.Many2one( "res.users", @@ -53,3 +56,32 @@ class EstateProperty(models.Model): buyer_id = fields.Many2one("res.partner", string=" Buyer") property_tag_ids = fields.Many2many("estate.property.tag") property_offer_ids = fields.One2many("estate.property.offer", "salesman_id") + total_area = fields.Float(compute="_compute_total_area", string="Total Area") + best_price = fields.Float( + string="Best Offer", + compute="_compute_best_price", + store=True, + ) + + @api.depends("garden_area", "living_area") + def _compute_total_area(self): + + for estate in self: + estate.total_area = estate.garden_area + estate.living_area + + @api.depends("property_offer_ids.price") + def _compute_best_price(self): + for record in self: + if record.property_offer_ids: + record.best_price = max(record.property_offer_ids.mapped("price")) + else: + record.best_price = 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 = "" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index bb9688281fd..bcec5b11d5f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -12,6 +12,21 @@ class EstatePropertyOffer(models.Model): copy=False, selection=[("acepted", "Accepted"), ("refused", "Refused")], ) + validity = fields.Integer(string="Validity(days)", default=7) + date_deadline = fields.Date( + compute="_sum_date", inverse="_compute_validity", string="Deadline", + ) salesman_id = fields.Many2one("res.partner", required=True, string="Partner") property_id = fields.Many2one("estate.property", required=True) + + @api.depends("validity") + def _sum_date(self): + + for record in self: + record.date_deadline = record.model.now() + record.validity + + def _compute_validity(self): + + for record in self: + record.validity = record.model.now() - record.date_deadline diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml index c041521ee56..8590ba36855 100644 --- a/estate/views/estate_property_offers_views.xml +++ b/estate/views/estate_property_offers_views.xml @@ -15,6 +15,7 @@ +
@@ -32,6 +33,7 @@

+

diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9c72800c1aa..aa74fc186c7 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -23,6 +23,7 @@ + @@ -42,6 +43,8 @@ + + @@ -71,6 +74,7 @@ + @@ -85,6 +89,7 @@ + @@ -92,6 +97,8 @@ + + From 96ac9f4ab6895d02aed69fa87de734d56ddd5e49 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Thu, 19 Feb 2026 12:22:56 +0530 Subject: [PATCH 10/16] [IMP] estate: implement action button and usererror function, covers(ch-9) - By adding action buttons , it's easy to have link between fields. UserError message helps to get alert message about the actions already done. This chapters allows us to link action to particular button such as: Actions button to sold,refused (property) and accept or reject (offers). User can also get error message if they do some illogical action. --- estate/models/estate_property.py | 24 +- estate/models/estate_property_offer.py | 41 +++- estate/models/estate_property_tag.py | 1 - estate/views/estate_property_offers_views.xml | 82 ++++--- estate/views/estate_property_tag_views.xml | 6 +- estate/views/estate_property_type_views.xml | 5 +- estate/views/estate_property_views.xml | 215 +++++++++--------- 7 files changed, 198 insertions(+), 176 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 899f3d5adb7..fd6e24ccc8f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -48,14 +49,30 @@ class EstateProperty(models.Model): ) property_type_id = fields.Many2one("estate.property.type", string="Property Type") + + def action_btn_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError(msg="property can't be cancelled") + record.state = "sold" + return True + + def action_btn_cancel(self): + for record in self: + if record.state == "sold": + raise UserError(msg="Cancelled property can't be sold") + record.state = "cancelled" + return True + salesman_id = fields.Many2one( "res.users", - string=" Salesman", + string="Salesman", default=lambda self: self.env.user, ) buyer_id = fields.Many2one("res.partner", string=" Buyer") - property_tag_ids = fields.Many2many("estate.property.tag") - property_offer_ids = fields.One2many("estate.property.offer", "salesman_id") + + property_offer_ids = fields.One2many("estate.property.offer", "property_id") + property_tag_ids = fields.Many2many("estate.property.tag", string="tag") total_area = fields.Float(compute="_compute_total_area", string="Total Area") best_price = fields.Float( string="Best Offer", @@ -65,7 +82,6 @@ class EstateProperty(models.Model): @api.depends("garden_area", "living_area") def _compute_total_area(self): - for estate in self: estate.total_area = estate.garden_area + estate.living_area diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index bcec5b11d5f..7d76863e619 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,7 @@ +from datetime import timedelta + from odoo import api, fields, models +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -7,26 +10,46 @@ class EstatePropertyOffer(models.Model): price = fields.Float(string="Price") property_offer_ids = fields.Integer(string="Offer") - state = fields.Selection( + status = fields.Selection( string="Status", copy=False, - selection=[("acepted", "Accepted"), ("refused", "Refused")], + selection=[("accepted", "Accepted"), ("refused", "Refused")], ) validity = fields.Integer(string="Validity(days)", default=7) date_deadline = fields.Date( - compute="_sum_date", inverse="_compute_validity", string="Deadline", + compute="_compute_sum_date", + inverse="_compute_validity", + string="Deadline", ) - salesman_id = fields.Many2one("res.partner", required=True, string="Partner") + def action_btn_accepted(self): + for record in self: + if record.status == "refused": + raise UserError(msg="Sorry offer sold out.") + record.status = "accepted" + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + return True + + def action_btn_refused(self): + for record in self: + if record.status == "accepted": + raise UserError(msg="Offer is already refudsed") + record.status = "refused" + record.property_id.selling_price = "0" + record.property_id.buyer_id = "" + return True + + partner_id = fields.Many2one("res.partner", required=True, string="Partner") property_id = fields.Many2one("estate.property", required=True) @api.depends("validity") - def _sum_date(self): - + def _compute_sum_date(self): for record in self: - record.date_deadline = record.model.now() + record.validity + record.date_deadline = fields.Date.today() + timedelta(days=record.validity) def _compute_validity(self): - for record in self: - record.validity = record.model.now() - record.date_deadline + fields.Date.today() == record.date_deadline - timedelta( + days=record.validity, + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 4f4261728b9..a96e97e6e9f 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,4 +6,3 @@ class EstatePropertyTag(models.Model): _description = "estate property tag" name = fields.Char(required=True) - property_tag_ids = fields.Integer() diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml index 8590ba36855..82ee6dc70d0 100644 --- a/estate/views/estate_property_offers_views.xml +++ b/estate/views/estate_property_offers_views.xml @@ -1,45 +1,43 @@ + + + Estate Property offer + estate.property.offer + list,form + - - - - Estate Property offer - estate.property.offer - list,form - - - - estate.property.offer.list - estate.property.offer - - - - - - + + estate.property.offer.list + estate.property.offer + + + + + + + +

+ + + + + + + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 08db2f551b1..0ca9546986e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,24 +4,26 @@ Estate Property estate.property list,form + {'search_default_availability' : True}
estate.property.list estate.property - + - - - - + - + + @@ -38,11 +40,13 @@ - + - + +
@@ -55,20 +59,21 @@
-

- +
- - + + @@ -89,14 +94,18 @@ - - + + - + + From 37f35b010fa67cb522f0aaa7cd0f87a3a2d29871 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Fri, 27 Feb 2026 19:10:27 +0530 Subject: [PATCH 13/16] [IMP] estate: implement python , model and view inheritance (ch-12) - CRUD opertions implemented using python inheritance. - res_users model is created which inherits the estate model feature. - It uses the same property list feature but of specific users such as admin. - Main feature of inheritance is res_user is sychronized with base field values. --- estate/__manifest__.py | 4 ++ estate/demo/demo_data.xml | 70 ++++++++++++++++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 15 +++--- estate/models/estate_property_offer.py | 19 +++++++ estate/models/res_users.py | 12 +++++ estate/views/res_users_view.xml | 23 +++++++++ 7 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 estate/demo/demo_data.xml create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d84b592558d..45473d5240b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,10 @@ "views/estate_property_tag_views.xml", "views/estate_property_offers_views.xml", "views/estate_menus.xml", + "views/res_users_view.xml", + ], + "demo": [ + "demo/demo_data.xml", ], "installable": True, "application": True, diff --git a/estate/demo/demo_data.xml b/estate/demo/demo_data.xml new file mode 100644 index 00000000000..e4faa3afc12 --- /dev/null +++ b/estate/demo/demo_data.xml @@ -0,0 +1,70 @@ + + + + Home + + + + Luxurious + + + + Chitrakut Residency + 900000 + 191980 + Best residency in this city + + + + + + Villa + + + + Cozy + + + + Rooftop House + 950000 + 191989 + It feels like heaven + + + + + + Penthouse + + + + Furnished + + + + Swadesh PG + 70000 + 191980 + Best PG in this city + + + + + + Palace + + + + Precious + + + + De glance Palace + 1000000 + 191970 + Great Indian Palace + + + + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f2c29d4a968..8733276da2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,4 +3,5 @@ estate_property_offer, # noqa: F401 estate_property_tag, # noqa: F401 estate_property_type, # noqa: F401 + res_users, # noqa: F401 ) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 20c7bee3d30..ed90d8b29ea 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -55,7 +55,7 @@ class EstateProperty(models.Model): def action_btn_sold(self): for record in self: if record.state == "cancelled": - raise UserError(_(msg="property can't be cancelled")) + raise UserError(_("property can't be cancelled")) record.state = "sold" return True @@ -129,11 +129,8 @@ def _check_selling_price_validation(self): ), ) - @api.ondelete(at_uninstall=False) - def unlink(self): - for record in self: - if record.state not in ("new", "cancelled"): - raise UserError(_("User can delete only new or cancelled property")) - if record.property_offer_ids: - raise UserError(_("Property can't be deleted")) - return super(EstateProperty, self).unlink() + @api.ondelete(at_uninstall=False) + def _unlink_if_not_allowed(self): + for record in self: + if record.state not in ("new", "cancelled"): + raise UserError(_("User can delete only new or cancelled property")) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5677bf2cbe4..5f57a21cfd2 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -65,3 +65,22 @@ def _compute_validity(self): "CHECK(price > 0)", "Offer Price field should always be positive", ) + + @api.model + def create(self, vals_list): + + for vals in vals_list: + property_id = vals.get("property_id") + price = vals.get("price") + + if property_id and price: + property_rec = self.env["estate.property"].browse(property_id) + + if property_rec.best_price and price <= property_rec.best_price: + raise UserError( + _("Offer price must be higher than the current best price."), + ) + if property_rec.state == "new": + property_rec.state = "offer_received" + + return super().create(vals_list) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..873200ccd06 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + "estate.property", + "salesman_id", + string="Available Properties", + # domain="[('state', 'in', ['new', 'offer_recieved'])]", + ) diff --git a/estate/views/res_users_view.xml b/estate/views/res_users_view.xml new file mode 100644 index 00000000000..074c21c0306 --- /dev/null +++ b/estate/views/res_users_view.xml @@ -0,0 +1,23 @@ + + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + + + + + + + + \ No newline at end of file From 8933d70f6b2b15b55dbc399b2767bbd0def329e1 Mon Sep 17 00:00:00 2001 From: pupat-odoo Date: Thu, 5 Mar 2026 12:33:41 +0530 Subject: [PATCH 14/16] [IMP] estate: implement invoice creation with link module estate_account (ch-13) - Created estate_account module by inheriting estate_property model. - Invoice generation enables customers to get the billing details of property. - invoice lines handles the commission and administrative fees. This chapter covers the inheritance part and also delivers how different modules link with each other. --- estate/__manifest__.py | 4 +- estate/data/estate_demo_data.xml | 70 ++++++ estate/demo/demo_data.xml | 70 ------ estate/models/estate_property.py | 57 ++--- estate/models/estate_property_offer.py | 61 +++-- estate/models/res_users.py | 1 - estate/views/estate_menus.xml | 49 ++-- estate/views/estate_property_offers_views.xml | 80 ++++--- estate/views/estate_property_tag_views.xml | 1 - estate/views/estate_property_type_views.xml | 3 +- estate/views/estate_property_views.xml | 214 +++++++++--------- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 12 + estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 32 +++ 15 files changed, 355 insertions(+), 301 deletions(-) create mode 100644 estate/data/estate_demo_data.xml delete mode 100644 estate/demo/demo_data.xml create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 45473d5240b..f1581e314e1 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,9 +13,7 @@ "views/estate_property_offers_views.xml", "views/estate_menus.xml", "views/res_users_view.xml", - ], - "demo": [ - "demo/demo_data.xml", + "data/estate_demo_data.xml", ], "installable": True, "application": True, diff --git a/estate/data/estate_demo_data.xml b/estate/data/estate_demo_data.xml new file mode 100644 index 00000000000..5102d4bbe82 --- /dev/null +++ b/estate/data/estate_demo_data.xml @@ -0,0 +1,70 @@ + + + + Home + + + + Luxurious + + + + Chitrakut Residency + 900000 + 191980 + Best residency in this city + + + + + + Villa + + + + Cozy + + + + Rooftop House + 950000 + 191989 + It feels like heaven + + + + + + Penthouse + + + + Furnished + + + + Swadesh PG + 70000 + 191980 + Best PG in this city + + + + + + Palace + + + + Precious + + + + De glance Palace + 1000000 + 191970 + Great Indian Palace + + + + diff --git a/estate/demo/demo_data.xml b/estate/demo/demo_data.xml deleted file mode 100644 index e4faa3afc12..00000000000 --- a/estate/demo/demo_data.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - Home - - - - Luxurious - - - - Chitrakut Residency - 900000 - 191980 - Best residency in this city - - - - - - Villa - - - - Cozy - - - - Rooftop House - 950000 - 191989 - It feels like heaven - - - - - - Penthouse - - - - Furnished - - - - Swadesh PG - 70000 - 191980 - Best PG in this city - - - - - - Palace - - - - Precious - - - - De glance Palace - 1000000 - 191970 - Great Indian Palace - - - - diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ed90d8b29ea..f8b53703e3d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -7,6 +7,7 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "estate property definition" _order = "id desc" + name = fields.Char(string="Property Name", required=True) description = fields.Text(string="Description", required=True) postcode = fields.Char(string="Postcode") @@ -52,20 +53,6 @@ class EstateProperty(models.Model): property_type_id = fields.Many2one("estate.property.type", string="Property Type") - def action_btn_sold(self): - for record in self: - if record.state == "cancelled": - raise UserError(_("property can't be cancelled")) - record.state = "sold" - return True - - def action_btn_cancel(self): - for record in self: - if record.state == "sold": - raise UserError(_("Cancelled property can't be sold")) - record.state = "cancelled" - return True - salesman_id = fields.Many2one( "res.users", string="Salesman", @@ -82,6 +69,11 @@ def action_btn_cancel(self): store=True, ) + _check_expected_price = models.Constraint( + "CHECK(expected_price > 0)", + "Expected price must be positive", + ) + @api.depends("garden_area", "living_area") def _compute_total_area(self): for estate in self: @@ -95,20 +87,6 @@ def _compute_best_price(self): else: record.best_price = 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 = "" - - _check_expected_price = models.Constraint( - "CHECK(expected_price > 0)", - "Expected price must be positive", - ) - @api.constrains("selling_price", "expected_price") def _check_selling_price_validation(self): for record in self: @@ -129,8 +107,31 @@ def _check_selling_price_validation(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 = "" + @api.ondelete(at_uninstall=False) def _unlink_if_not_allowed(self): for record in self: if record.state not in ("new", "cancelled"): raise UserError(_("User can delete only new or cancelled property")) + + def action_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError(_("property can't be cancelled")) + record.state = "sold" + return True + + def action_cancel(self): + for record in self: + if record.state == "sold": + raise UserError(_("Cancelled property can't be sold")) + record.state = "cancelled" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5f57a21cfd2..afe92c86d7d 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -23,24 +23,6 @@ class EstatePropertyOffer(models.Model): string="Deadline", ) - def action_btn_accepted(self): - for record in self: - if record.status == "refused": - raise UserError(_("Offer is already refused")) - record.status = "accepted" - record.property_id.selling_price = record.price - record.property_id.buyer_id = record.partner_id - return True - - def action_btn_refused(self): - for record in self: - if record.status == "accepted": - raise UserError(_("Sorry offer sold out.")) - record.status = "refused" - record.property_id.selling_price = 0 - record.property_id.buyer_id = "" - return True - partner_id = fields.Many2one("res.partner", required=True, string="Partner") property_id = fields.Many2one("estate.property", required=True) property_type_id = fields.Many2one( @@ -55,16 +37,22 @@ def _compute_sum_date(self): for record in self: record.date_deadline = fields.Date.today() + timedelta(days=record.validity) + _check_price = models.Constraint( + "CHECK(price > 0)", + "Offer Price field should always be positive", + ) + def _compute_validity(self): for record in self: fields.Date.today() == record.date_deadline - timedelta( days=record.validity, ) - _check_price = models.Constraint( - "CHECK(price > 0)", - "Offer Price field should always be positive", - ) + @api.onchange("date_deadline") + def _onchange_validity(self): + if self.date_deadline: + create_date = fields.Date.to_date(self.create_date) or fields.Date.today() + self.validity = (self.date_deadline - create_date).days @api.model def create(self, vals_list): @@ -84,3 +72,32 @@ def create(self, vals_list): property_rec.state = "offer_received" return super().create(vals_list) + + def action_accepted(self): + accepted_records = self.search_count( + [ + ("property_id", "=", self.property_id), + ("status", "=", "accepted"), + ], + limit=1, + ) + if accepted_records: + raise UserError(_(" multiple offer can't be accepted")) + + self.status = "accepted" + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id + self.property_id.state = "offer_accepted" + other_offers = self.search( + [ + ("property_id", "=", self.property_id), + ("status", "!=", "accepted"), + ], + ) + other_offers.write({"status": "refused"}) + return True + + def action_refused(self): + for record in self: + record.status = "refused" + return True diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 873200ccd06..7a1e696367b 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -8,5 +8,4 @@ class ResUsers(models.Model): "estate.property", "salesman_id", string="Available Properties", - # domain="[('state', 'in', ['new', 'offer_recieved'])]", ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 6f3562593c4..1c32d774ffe 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -9,30 +9,27 @@ list,form - - - - - - - - - - - - - + + + + + + + + + + diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml index 3760ef961e0..16c4ce521c0 100644 --- a/estate/views/estate_property_offers_views.xml +++ b/estate/views/estate_property_offers_views.xml @@ -1,48 +1,46 @@ - - + + Estate Property offer estate.property.offer list,form [('property_type_id', '=', active_id)] - + - - estate.property.offer.list - estate.property.offer - - - - - - -