From e73b45838b7c9b675671b0150f7a6b54894c0550 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Wed, 31 Dec 2025 19:21:02 +0530 Subject: [PATCH 01/14] =?UTF-8?q?[ADD]=20estate:=20complete=20Chapters=201?= =?UTF-8?q?=E2=80=935=20of=20Odoo=5FDemo=20tutorial?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Architecture Overview - A New application - Models And Basic Fields - Security - A Brief Introduction - Finally, Some UI To Play With --- estate/__init__.py | 1 + estate/__manifest__.py | 10 ++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 23 +++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 23 +++++++++++++++++++++++ 7 files changed, 67 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..31234a3b3fb --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name':'estate', + 'depends':['base'], + 'application' : True, + 'data':[ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' + ] +} \ No newline at end of file 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..68bc1ad2745 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,23 @@ +from odoo import models,fields +from datetime import date, timedelta + +class Property(models.Model): + _name = "estate.property" + _description = "estate property details" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(copy=False,default=lambda self: fields.Date.today() + timedelta(days=90)) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True,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'),('west','West'),('east','East'),('south','South')]) + active = fields.Boolean(default=False) + state = fields.Selection(default='New',selection=[('New','New'),('Offer Received','Offer Received'), + ('Offer Accepted','Offer Accepted'),('Sold','Sold'),('Cancelled','Cancelled')],copy=False,required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..66829c09648 --- /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 +model_estate,model_estate,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..a32e332b351 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..82323434561 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,23 @@ + + + EstateProperty list + estate.property + + + + + + + + + + + + + + + EstateProperty action + estate.property + list + + \ No newline at end of file From 40526e2344e40c5eb7a68e53ace9ce0ec8435dba Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Thu, 1 Jan 2026 11:08:51 +0530 Subject: [PATCH 02/14] =?UTF-8?q?[ADD]=20estate:=20completed=20Chapters=20?= =?UTF-8?q?1=E2=80=935=20of=20Odoo=20tutorial=20Demo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Covered Odoo architecture concepts - Initialized a new application - Implemented core models and fields - Added basic security configuration - Created simple UI views to interact with data --- estate/__init__.py | 2 +- estate/__manifest__.py | 15 +++++++++------ estate/models/estate_property.py | 32 ++++++++++++++++++++++++-------- 3 files changed, 34 insertions(+), 15 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 31234a3b3fb..a334456fd04 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,13 @@ { - 'name':'estate', - 'depends':['base'], - 'application' : True, - 'data':[ + 'name': 'estate', + 'depends': ['base'], + 'application': True, + 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_menus.xml' - ] -} \ No newline at end of file + ], + 'author': 'pkhu', + 'license': 'LGPL-3', + +} diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 68bc1ad2745..4f9e230be42 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import models,fields -from datetime import date, timedelta +from odoo import models, fields +from datetime import timedelta class Property(models.Model): _name = "estate.property" @@ -8,16 +8,32 @@ class Property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False,default=lambda self: fields.Date.today() + timedelta(days=90)) - expected_price = fields.Float(required=True) - selling_price = fields.Float(readonly=True,copy=False) + date_availability = fields.Date(copy=False, default=lambda self: fields.Date.today() + timedelta(days=90)) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, 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'),('west','West'),('east','East'),('south','South')]) + garden_orientation = fields.Selection( + selection=[ + ('north', 'North'), + ('west', 'West'), + ('east', 'East'), + ('south', 'South') + ]) active = fields.Boolean(default=False) - state = fields.Selection(default='New',selection=[('New','New'),('Offer Received','Offer Received'), - ('Offer Accepted','Offer Accepted'),('Sold','Sold'),('Cancelled','Cancelled')],copy=False,required=True) \ No newline at end of file + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], + default='new', + copy=False, + required=True + ) From 65548f0e5219f8cc9bc1c6e70cecc9cf29c119b2 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Thu, 1 Jan 2026 18:13:35 +0530 Subject: [PATCH 03/14] [ADD] estate: completed Chapter 6 (Basic views setup for custom model) - Created custom XML views (list and form) for estate.property model - Defined logical field arrangement instead of defaults - Enabled searching across multiple fields - Added postcode-based filter - Added group by for record status: New and Offer Received --- estate/__manifest__.py | 21 +++-- estate/models/estate_property.py | 30 ++++--- estate/views/estate_property_views.xml | 106 ++++++++++++++++++++----- 3 files changed, 113 insertions(+), 44 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a334456fd04..86e3686c672 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,13 +1,12 @@ { - 'name': 'estate', - 'depends': ['base'], - 'application': True, - 'data': [ - 'security/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_menus.xml' - ], - 'author': 'pkhu', - 'license': 'LGPL-3', - + "name": "estate", + "depends": ["base"], + "application": True, + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", + ], + "author": "pkhu", + "license": "LGPL-3", } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4f9e230be42..8c0e72eeab2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from odoo import models, fields from datetime import timedelta + class Property(models.Model): _name = "estate.property" _description = "estate property details" @@ -8,7 +9,9 @@ class Property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=lambda self: fields.Date.today() + timedelta(days=90)) + date_availability = fields.Date( + copy=False, default=lambda self: fields.Date.today() + timedelta(days=90) + ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -19,21 +22,22 @@ class Property(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( selection=[ - ('north', 'North'), - ('west', 'West'), - ('east', 'East'), - ('south', 'South') - ]) + ("north", "North"), + ("west", "West"), + ("east", "East"), + ("south", "South"), + ] + ) active = fields.Boolean(default=False) state = fields.Selection( selection=[ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled') + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), ], - default='new', + default="new", copy=False, - required=True + required=True, ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 82323434561..4b86e79a701 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,23 +1,89 @@ + - - EstateProperty list - estate.property - - - - - - - - - - - - + + + Properties list + estate.property + + + + + + + + + + + + - - EstateProperty action - estate.property - list - + + Properties + estate.property + +
+ + + +

+ +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + Properties Search + estate.property + + + + + + + + + + + + + + + + + + + Properties + estate.property + list,form + +
\ No newline at end of file From ddda1fde3a8ae6ef5538145c542abc376882a475 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Fri, 2 Jan 2026 15:37:17 +0530 Subject: [PATCH 04/14] [ADD] estate: completed Chapter 7 (Relations between models) - Added new models for property type, tags, and offers - Added many2one fields for property type, buyer and salesperson - Added many2many relation for property tags with appropriate widget - Implemented one2many relation for property offers - Updated views and menus accordingly --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 5 +++ estate/models/estate_property_offer.py | 13 ++++++ estate/models/estate_property_tag.py | 8 ++++ estate/models/etsate_property_type.py | 8 ++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 14 ++++-- estate/views/estate_property_offer_views.xml | 37 ++++++++++++++++ estate/views/estate_property_tag_views.xml | 9 ++++ estate/views/estate_property_type_views.xml | 46 ++++++++++++++++++++ estate/views/estate_property_views.xml | 33 ++++++++++++-- 12 files changed, 175 insertions(+), 7 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/etsate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 86e3686c672..e0ad5b51eab 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,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_offer_views.xml", "views/estate_menus.xml", ], "author": "pkhu", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..a5aec6f5886 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ +from . import etsate_property_type from . import estate_property +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 8c0e72eeab2..b439501b052 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -41,3 +41,8 @@ class Property(models.Model): copy=False, required=True, ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + user_id = fields.Many2one("res.users", string="Salesperson") + partner_id = fields.Many2one("res.partner", string="Buyer") + tag_ids = fields.Many2many("estate.property.tag", string="Tags") + offer_ids = fields.One2many("estate.property.offer", "property_id") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..3ef38620ea3 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,13 @@ +from odoo import models, fields + + +class PropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Property offer for each property." + + price = fields.Float() + status = fields.Selection( + [("Accepted", "Accepted"), ("Refused", "Refused")], copy=False + ) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..dc7fa8dfbf5 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class PropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Property Tags to describe property such new, renovated..." + + name = fields.Char(required=True) diff --git a/estate/models/etsate_property_type.py b/estate/models/etsate_property_type.py new file mode 100644 index 00000000000..81fd1ab6576 --- /dev/null +++ b/estate/models/etsate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class PropertyType(models.Model): + _name = "estate.property.type" + _description = "Define Type of property (House, Apartment, Penthouse, Castle…)" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 66829c09648..1ee8aac2bdb 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 model_estate,model_estate,model_estate_property,base.group_user,1,1,1,1 +model_estate_type,model_estate_type,model_estate_property_type,base.group_user,1,1,1,1 +model_estate_tag,model_estate_tag,model_estate_property_tag,base.group_user,1,1,1,1 +model_estate_offer,model_estate_offer,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 a32e332b351..4dd170c5657 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,15 @@ - - - + + + + + + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..32097cb1de4 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,37 @@ + + + + Properties offer + estate.property.offer + + + + + + + + + + + Property offer + estate.property.offer + +
+ + + + + + + +
+
+
+ + + Property Offer + estate.property.offer + list,form + + +
\ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..fdfaf000eda --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,9 @@ + + + + Property Tags + estate.property.tag + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..8e5a2cf2a22 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,46 @@ + + + + Properties Type list + estate.property.type + + + + + + + + + Properties + estate.property.type + +
+ + +

+ +

+
+
+
+
+
+ + + Properties Search + estate.property.type + + + + + + + + + + Property Type + 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 4b86e79a701..95ab34cc4fe 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,12 +1,15 @@ - + + Properties list estate.property + + @@ -17,7 +20,7 @@ - + Properties estate.property @@ -32,6 +35,15 @@ +

+ + +

+
+
+ + + @@ -54,18 +66,30 @@ + + + + + + + + + + +
- + Properties Search estate.property + @@ -80,10 +104,11 @@ - + Properties estate.property list,form +
\ No newline at end of file From 2d40394d123f0c6be7960fb23b1ab52aa6b392dc Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Mon, 5 Jan 2026 18:32:33 +0530 Subject: [PATCH 05/14] [ADD] estate: completed Chapter 8 ( Computed Fields And Onchanges ) - Implemented computed fields (total area, best offer, Deadline date) - Added inverse methods for validity - Created onchange to auto-update garden area and orientation --- estate/__manifest__.py | 24 +-- estate/models/estate_property.py | 64 ++++-- estate/models/estate_property_offer.py | 28 ++- estate/models/estate_property_tag.py | 4 +- estate/models/etsate_property_type.py | 4 +- estate/views/estate_property_offer_views.xml | 10 +- estate/views/estate_property_tag_views.xml | 2 +- estate/views/estate_property_type_views.xml | 8 +- estate/views/estate_property_views.xml | 197 +++++++++---------- 9 files changed, 192 insertions(+), 149 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e0ad5b51eab..c91f552616f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,15 +1,15 @@ { - "name": "estate", - "depends": ["base"], - "application": True, - "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_offer_views.xml", - "views/estate_menus.xml", + 'name': "estate", + 'author': 'pkhu', + 'license': 'LGPL-3', + 'depends': ['base'], + 'application': True, + '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_offer_views.xml', + 'views/estate_menus.xml', ], - "author": "pkhu", - "license": "LGPL-3", } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b439501b052..a8f314783c2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,11 @@ -from odoo import models, fields from datetime import timedelta +from odoo import models, fields, api + class Property(models.Model): - _name = "estate.property" - _description = "estate property details" + _name = 'estate.property' + _description = 'estate property details' name = fields.Char(required=True) description = fields.Text() @@ -15,34 +16,57 @@ class Property(models.Model): expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) - living_area = fields.Integer() + living_area = fields.Integer(string="Living Area(sqm)") facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() + garden_area = fields.Integer(string="Garden Area (sqm)") garden_orientation = fields.Selection( selection=[ - ("north", "North"), - ("west", "West"), - ("east", "East"), - ("south", "South"), + ('north', "North"), + ('west', "West"), + ('east', "East"), + ('south', "South"), ] ) active = fields.Boolean(default=False) state = fields.Selection( selection=[ - ("new", "New"), - ("offer_received", "Offer Received"), - ("offer_accepted", "Offer Accepted"), - ("sold", "Sold"), - ("cancelled", "Cancelled"), + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled"), ], - default="new", + default='new', copy=False, required=True, ) - property_type_id = fields.Many2one("estate.property.type", string="Property Type") - user_id = fields.Many2one("res.users", string="Salesperson") - partner_id = fields.Many2one("res.partner", string="Buyer") - tag_ids = fields.Many2many("estate.property.tag", string="Tags") - offer_ids = fields.One2many("estate.property.offer", "property_id") + property_type_id = fields.Many2one('estate.property.type', string="Property Type") + user_id = fields.Many2one('res.users', string="Salesperson") + partner_id = fields.Many2one('res.partner', string="Buyer") + tag_ids = fields.Many2many('estate.property.tag', string="Tags") + offer_ids = fields.One2many('estate.property.offer', 'property_id') + total_area = fields.Integer(compute='_compute_total_area', string="Total Area(sqm)") + best_price = fields.Float(compute='_compute_best_price') + + @api.depends('garden_area', 'living_area') + def _compute_total_area(self): + self.total_area = self.living_area + self.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + if record.offer_ids.mapped('price'): + record.best_price = max(record.offer_ids.mapped('price')) + else: + record.best_price = None + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = None + self.garden_orientation = None diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 3ef38620ea3..aa852052a57 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,13 +1,29 @@ -from odoo import models, fields +from datetime import timedelta + +from odoo import models, fields, api class PropertyOffer(models.Model): - _name = "estate.property.offer" - _description = "Property offer for each property." + _name = 'estate.property.offer' + _description = 'Property offer for each property.' price = fields.Float() status = fields.Selection( - [("Accepted", "Accepted"), ("Refused", "Refused")], copy=False + selection=[('Accepted', "Accepted"), ('Refused', "Refused")], copy=False + ) + partner_id = fields.Many2one('res.partner', string="Partner", required=True) + property_id = fields.Many2one('estate.property', string="Property", required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute='_compute_deadline', inverse='_inverse_deadline' ) - partner_id = fields.Many2one("res.partner", string="Partner", required=True) - property_id = fields.Many2one("estate.property", string="Property", required=True) + + @api.depends('validity') + def _compute_deadline(self): + self.date_deadline = self.create_date + timedelta(days=self.validity) + + def _inverse_deadline(self): + if self.date_deadline: + self.validity = ( + self.date_deadline - self.create_date.date() + ).days diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index dc7fa8dfbf5..ca259afcf1d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -2,7 +2,7 @@ class PropertyTag(models.Model): - _name = "estate.property.tag" - _description = "Property Tags to describe property such new, renovated..." + _name = 'estate.property.tag' + _description = 'Property Tags to describe property such new, renovated...' name = fields.Char(required=True) diff --git a/estate/models/etsate_property_type.py b/estate/models/etsate_property_type.py index 81fd1ab6576..cbab98ed197 100644 --- a/estate/models/etsate_property_type.py +++ b/estate/models/etsate_property_type.py @@ -2,7 +2,7 @@ class PropertyType(models.Model): - _name = "estate.property.type" - _description = "Define Type of property (House, Apartment, Penthouse, Castle…)" + _name = 'estate.property.type' + _description = 'Define Type of property (House, Apartment, Penthouse, Castle…)' name = fields.Char(required=True) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 32097cb1de4..5f30f99e287 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,6 +1,6 @@ - + Properties offer estate.property.offer @@ -8,11 +8,13 @@ + + - + Property offer estate.property.offer @@ -22,6 +24,8 @@ + + @@ -34,4 +38,4 @@ list,form - \ No newline at end of file + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index fdfaf000eda..4788dc9c1b0 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -6,4 +6,4 @@ list,form - \ No newline at end of file + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 8e5a2cf2a22..f23827dcb5d 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -1,6 +1,6 @@ - + Properties Type list estate.property.type @@ -10,7 +10,7 @@ - + Properties estate.property.type @@ -26,7 +26,7 @@ - + Properties Search estate.property.type @@ -43,4 +43,4 @@ 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 95ab34cc4fe..d172135dedf 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,114 +1,113 @@ - + - - - Properties list - estate.property - - - - - - - - - - - - - - + + Properties list + estate.property + + + + + + + + + + + + + + - - Properties - estate.property - -
- + + Properties + estate.property + + + + - -

- -

-
+

+ +

+
+ - -

- - -

-
+

+ +

+
+ + + + + + + + + + + + + - - - + + + + + + + + + + + + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
+ + + + + + + + +
+ +
+
- - Properties Search - estate.property - - - - - - - - - - - - - - - - - + + Properties Search + estate.property + + + + + + + + + + + + + + + + + - - Properties - estate.property - list,form - + + Properties + estate.property + list,form + -
-
\ No newline at end of file + From a6cbe6b3953bf54b58d87ac5f41d2ad2a6d4323c Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Wed, 7 Jan 2026 14:12:32 +0530 Subject: [PATCH 06/14] [ADD] estate: completed Chapter 9 and 10 ( Action and Constraints ) - Added action buttons (Cancel, Sell, Accept, Refuse) linked to model methods - Implemented state transitions for properties and offers via actions - Automatically set selling price and buyer on offer acceptance - Added restrictions (e.g., prevent selling canceled properties) - Added SQL constraints for positive prices, unique property names, etc. - Implemented Python constraints for selling price --- estate/__manifest__.py | 4 +- estate/models/__init__.py | 4 +- estate/models/estate_property.py | 31 ++++++++++++- estate/models/estate_property_offer.py | 46 +++++++++++++++++--- estate/models/estate_property_tag.py | 5 +++ estate/models/etsate_property_type.py | 5 +++ estate/views/estate_property_offer_views.xml | 10 +++-- estate/views/estate_property_type_views.xml | 6 +-- estate/views/estate_property_views.xml | 12 +++-- 9 files changed, 100 insertions(+), 23 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index c91f552616f..4134988027f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,7 +1,7 @@ { 'name': "estate", - 'author': 'pkhu', - 'license': 'LGPL-3', + 'author': "pkhu", + 'license': "LGPL-3", 'depends': ['base'], 'application': True, 'data': [ diff --git a/estate/models/__init__.py b/estate/models/__init__.py index a5aec6f5886..a83cd0e71f8 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,4 @@ -from . import etsate_property_type from . import estate_property -from . import estate_property_tag from . import estate_property_offer +from . import estate_property_tag +from . import etsate_property_type diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a8f314783c2..62e9f16c4c9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from datetime import timedelta from odoo import models, fields, api +from odoo.exceptions import UserError, ValidationError class Property(models.Model): @@ -44,12 +45,21 @@ class Property(models.Model): ) property_type_id = fields.Many2one('estate.property.type', string="Property Type") user_id = fields.Many2one('res.users', string="Salesperson") - partner_id = fields.Many2one('res.partner', string="Buyer") + partner_id = fields.Many2one('res.partner', string="Buyer", readonly=True) tag_ids = fields.Many2many('estate.property.tag', string="Tags") offer_ids = fields.One2many('estate.property.offer', 'property_id') total_area = fields.Integer(compute='_compute_total_area', string="Total Area(sqm)") best_price = fields.Float(compute='_compute_best_price') + _check_expected_price = models.Constraint( + 'CHECK(expected_price > 0)', + "The Expected price cannot be negative or zero." + ) + _check_selling_price = models.Constraint( + 'CHECK(selling_price > 0)', + "The Selling price cannot be negative." + ) + @api.depends('garden_area', 'living_area') def _compute_total_area(self): self.total_area = self.living_area + self.garden_area @@ -66,7 +76,24 @@ def _compute_best_price(self): def _onchange_garden(self): if self.garden: self.garden_area = 10 - self.garden_orientation = 'north' + self.garden_orientation = "north" else: self.garden_area = None self.garden_orientation = None + + @api.constrains('selling_price', 'expected_price') + def _constraint_selling_price(self): + if self.partner_id and self.selling_price < (self.expected_price * .9): + raise ValidationError("Selling price cannot be lower than 90% of the expected price.") + + def action_property_sold(self): + if self.state == 'cancelled': + raise UserError("Cancelled property cannot be sold.") + else: + self.state = "sold" + + def action_property_cancel(self): + if self.state == 'sold': + raise UserError("Sold property cannot be Cancelled.") + else: + self.state = "cancelled" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index aa852052a57..eba095c84b8 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from datetime import timedelta from odoo import models, fields, api +from odoo.exceptions import UserError, ValidationError class PropertyOffer(models.Model): @@ -9,7 +10,7 @@ class PropertyOffer(models.Model): price = fields.Float() status = fields.Selection( - selection=[('Accepted', "Accepted"), ('Refused', "Refused")], copy=False + selection=[('accepted', "Accepted"), ('refused', "Refused")], copy=False ) partner_id = fields.Many2one('res.partner', string="Partner", required=True) property_id = fields.Many2one('estate.property', string="Property", required=True) @@ -18,12 +19,45 @@ class PropertyOffer(models.Model): compute='_compute_deadline', inverse='_inverse_deadline' ) + _check_price = models.Constraint( + 'CHECK(price >= 0)', + "The Offer price cannot be negative." + ) + @api.depends('validity') def _compute_deadline(self): - self.date_deadline = self.create_date + timedelta(days=self.validity) + for record in self: + if record.create_date: + record.date_deadline = record.create_date + timedelta(days=record.validity) + else: + record.date_deadline = fields.Date.today() + timedelta(days=record.validity) def _inverse_deadline(self): - if self.date_deadline: - self.validity = ( - self.date_deadline - self.create_date.date() - ).days + for record in self: + if record.date_deadline: + record.validity = ( + record.date_deadline - record.create_date.date() + ).days + else: + record.validity = (record.date_deadline - fields.Date.today()) + + def action_accepted(self): + accepect_records = self.property_id.offer_ids.filtered( lambda o: o.status == 'accepted') + if accepect_records: + raise UserError("Only one offer can be accepted.") + self.status = "accepted" + self.property_id.partner_id = self.partner_id + self.property_id.selling_price = self.price + self.property_id.state = 'offer_accepted' + + def action_refused(self): + if self.status == 'accepted': + self.property_id.partner_id = None + self.property_id.selling_price = None + self.property_id.state = 'offer_received' + self.status = "refused" + + @api.ondelete(at_uninstall=False) + def _ondelete_offer(self): + if self.status == 'accepted': + raise ValidationError('Accepted offer cannot be deleted.') diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index ca259afcf1d..67cc892aef2 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,3 +6,8 @@ class PropertyTag(models.Model): _description = 'Property Tags to describe property such new, renovated...' name = fields.Char(required=True) + + _unique_name = models.Constraint( + 'UNIQUE(name)', + "Property Tag must be unique." + ) diff --git a/estate/models/etsate_property_type.py b/estate/models/etsate_property_type.py index cbab98ed197..860e99f67a6 100644 --- a/estate/models/etsate_property_type.py +++ b/estate/models/etsate_property_type.py @@ -6,3 +6,8 @@ class PropertyType(models.Model): _description = 'Define Type of property (House, Apartment, Penthouse, Castle…)' name = fields.Char(required=True) + + _unique_name = models.Constraint( + 'UNIQUE(name)', + "Property Type must be unique." + ) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 5f30f99e287..fcf3fdeb57d 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,21 +1,23 @@ - Properties offer + estate.property.offer.view.list estate.property.offer - + + -

- -

+ +

+ +

+
+ + + + + + + + + + +
- + estate.property.type.view.search estate.property.type diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 35b5b9efd3f..3c40b45db70 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,16 +5,17 @@ estate.property.view.list estate.property - + - + - + + @@ -25,8 +26,9 @@
-
@@ -39,13 +41,13 @@

- +

- + @@ -72,7 +74,7 @@ - + @@ -97,10 +99,11 @@ - + - + + @@ -112,6 +115,7 @@ Properties estate.property list,form + {'search_default_best_price': 1} From d8e37e7f7609c048eb8c67076212e60effbbdedc Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Mon, 12 Jan 2026 14:32:42 +0530 Subject: [PATCH 09/14] [ADD] estate: Completed task (Property maintenance model) - Created maintenance request model linked to properties - Added workflow states: new, approved, done - Added validation for approved maintenance cost - Computed total maintenance cost per property - Restricted selling properties with unfinished maintenance --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 16 ++++++++++++++ estate/models/estate_property_maintenance.py | 20 +++++++++++++++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_menus.xml | 1 + .../estate_property_maintenance_view.xml | 22 +++++++++++++++++++ estate/views/estate_property_views.xml | 14 ++++++++++++ 8 files changed, 76 insertions(+) create mode 100644 estate/models/estate_property_maintenance.py create mode 100644 estate/views/estate_property_maintenance_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 66105957168..bec8ac9c846 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,7 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_maintenance_view.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index a83cd0e71f8..28ddfb70cbc 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_offer from . import estate_property_tag from . import etsate_property_type +from . import estate_property_maintenance diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f340af530a5..470ae29191b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -55,6 +55,10 @@ class Property(models.Model): total_area = fields.Integer( compute='_compute_total_area', string="Total Area(sqm)") best_price = fields.Float(compute='_compute_best_price', store=True) + property_maintainance_ids = fields.One2many( + 'estate.property.maintenance', 'property_id') + total_maintenance_cost = fields.Float( + compute='_compute_total_maintenance_cost') _check_expected_price = models.Constraint( 'CHECK(expected_price > 0)', @@ -98,6 +102,9 @@ def action_property_sold(self): if self.state == 'cancelled': raise UserError("Cancelled property cannot be sold.") else: + for record in self.property_maintainance_ids: + if record.status != 'done': + raise UserError("Maintenance Request are still pending.") self.state = "sold" def action_property_cancel(self): @@ -105,3 +112,12 @@ def action_property_cancel(self): raise UserError("Sold property cannot be Cancelled.") else: self.state = "cancelled" + + @api.depends('property_maintainance_ids.cost') + def _compute_total_maintenance_cost(self): + for record in self: + if record.property_maintainance_ids: + record.total_maintenance_cost = sum( + record.property_maintainance_ids.mapped('cost')) + else: + record.total_maintenance_cost = 0.00 diff --git a/estate/models/estate_property_maintenance.py b/estate/models/estate_property_maintenance.py new file mode 100644 index 00000000000..526ddd0cb75 --- /dev/null +++ b/estate/models/estate_property_maintenance.py @@ -0,0 +1,20 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_is_zero + + +class PropertyMantainance(models.Model): + _name = 'estate.property.maintenance' + _description = 'show propety maintenance request' + + name = fields.Char(string="Title", required=True) + cost = fields.Float() + status = fields.Selection(selection=[( + 'new', "New"), ('approved', "Approved"), ('done', "Done")], default='new') + property_id = fields.Many2one('estate.property') + + @api.onchange('status') + def _onchenge_status(self): + for record in self: + if record.status == 'approved' and float_is_zero(record.cost, precision_rounding=0.01): + raise UserError("Cost must be greater than zero.") diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 1ee8aac2bdb..9c9bf8a21a2 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -3,3 +3,4 @@ model_estate,model_estate,model_estate_property,base.group_user,1,1,1,1 model_estate_type,model_estate_type,model_estate_property_type,base.group_user,1,1,1,1 model_estate_tag,model_estate_tag,model_estate_property_tag,base.group_user,1,1,1,1 model_estate_offer,model_estate_offer,model_estate_property_offer,base.group_user,1,1,1,1 +model_estate_maintenance,model_estate_maintenance,model_estate_property_maintenance,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 4dd170c5657..683d0504aad 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -9,6 +9,7 @@ + diff --git a/estate/views/estate_property_maintenance_view.xml b/estate/views/estate_property_maintenance_view.xml new file mode 100644 index 00000000000..2d5ec88bd95 --- /dev/null +++ b/estate/views/estate_property_maintenance_view.xml @@ -0,0 +1,22 @@ + + + + estate.property.maintenance.view.list + estate.property.maintenance + + + + + + + + + + + + Maintenance Request + estate.property.maintenance + 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 3c40b45db70..4ef3331a2f5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -83,6 +83,20 @@ + + + + + + + + + + + + + +
From c053845c31776d1f583177983716bda7fb10ea74 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Fri, 16 Jan 2026 14:04:43 +0530 Subject: [PATCH 10/14] [ADD] estate: completed Chapter 12 (Inheritance) - Added @ondelete decorator to restrict property deletion - Override create to update property state on offer - Add property_ids field to res.users via model inheritance - Inherit Users view to display properties using xpath --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 5 ++++ estate/models/estate_property_maintenance.py | 2 +- estate/models/estate_property_offer.py | 29 ++++++++++++++------ estate/models/res_users.py | 7 +++++ estate/views/estate_property_views.xml | 2 +- estate/views/res_users_views.xml | 25 +++++++++++++++++ 8 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index bec8ac9c846..ee6ff2a5cca 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,6 +11,7 @@ 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', + 'views/res_users_views.xml', 'views/estate_menus.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 28ddfb70cbc..465e23d68d5 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,3 +3,4 @@ from . import estate_property_tag from . import etsate_property_type from . import estate_property_maintenance +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 470ae29191b..f6a4d667189 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -121,3 +121,8 @@ def _compute_total_maintenance_cost(self): record.property_maintainance_ids.mapped('cost')) else: record.total_maintenance_cost = 0.00 + + @api.ondelete(at_uninstall=False) + def _unlink_property(self): + if self.state not in ['new', 'cancelled']: + raise UserError("Only new and cancelled property can be deleted.") diff --git a/estate/models/estate_property_maintenance.py b/estate/models/estate_property_maintenance.py index 526ddd0cb75..8d0a546e395 100644 --- a/estate/models/estate_property_maintenance.py +++ b/estate/models/estate_property_maintenance.py @@ -14,7 +14,7 @@ class PropertyMantainance(models.Model): property_id = fields.Many2one('estate.property') @api.onchange('status') - def _onchenge_status(self): + def _onchange_status(self): for record in self: if record.status == 'approved' and float_is_zero(record.cost, precision_rounding=0.01): raise UserError("Cost must be greater than zero.") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b91531875c4..21ba3fad733 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -2,6 +2,7 @@ from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError +from odoo.tools.float_utils import float_compare class PropertyOffer(models.Model): @@ -22,8 +23,8 @@ class PropertyOffer(models.Model): compute='_compute_date_deadline', inverse='_inverse_date_deadline' ) property_type_id = fields.Many2one( - "estate.property.type", - related="property_id.property_type_id", + 'estate.property.type', + related='property_id.property_type_id', store=True ) @@ -36,7 +37,8 @@ class PropertyOffer(models.Model): def _compute_date_deadline(self): for record in self: if record.create_date: - record.date_deadline = record.create_date + timedelta(days=record.validity) + record.date_deadline = record.create_date + \ + timedelta(days=record.validity) else: record.date_deadline = fields.Date.today() + timedelta(days=record.validity) @@ -70,11 +72,22 @@ def action_refused(self): def _ondelete_offer(self): for records in self: if records.status == 'accepted': - raise ValidationError('Accepted offer cannot be deleted.') + raise ValidationError("Accepted offer cannot be deleted.") + @api.model def create(self, vals): - offer = super().create(vals) - if offer.property_id and offer.property_id.state == 'new': - offer.property_id.state = 'offer_received' + for val in vals: + price = val.get('price') + property_id = val.get('property_id') + property = self.env['estate.property'].browse(property_id) + if property.state == "new": + property.best_price = price + elif float_compare(price, property.best_price, precision_rounding=0.01) < 0: + raise UserError( + f"Price should be greater than {property.best_price}") + else: + property.best_price = price + if property and property.state == 'new': + property.state = 'offer_received' - return offer + return super().create(vals) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..4fa1dc736ff --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many('estate.property', 'user_id') diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4ef3331a2f5..5224c945771 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -8,7 +8,7 @@ - + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..a73b7934e09 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,25 @@ + + + res.users.view.form.inherit.property + res.users + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From b386956b41564f1c029c8748511f1da2654dd4f9 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Mon, 19 Jan 2026 11:40:31 +0530 Subject: [PATCH 11/14] [ADD] estate: completed Chapter 13 (Interact With Other Modules) - Created estate_account link module depending on estate and account - Extended estate.property to override sell action - Generate customer invoice on property sold - Added invoice lines for property price and other fees --- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 8 +++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 27 ++++++++++++++++++++++++ 4 files changed, 37 insertions(+) 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_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..e8164f1b75c --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,8 @@ +{ + 'name': "estate_account", + 'author': "pkhu", + 'license': "LGPL-3", + 'depends': ['estate', 'account'], + 'application': True, + 'data': [], +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..22afeae115f --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import models, Command + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_property_sold(self): + res = super().action_property_sold() + for property in self: + vals = { + 'partner_id': property.partner_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create({ + 'name': property.name, + 'quantity': 1, + 'price_unit': property.selling_price * 0.06 + }), + Command.create({ + 'name': "Administrative fees", + 'quantity': 1, + 'price_unit': 100 + }), + ] + } + self.env['account.move'].create(vals) + return res From 47c81634e3286cb5b7a3335d0bc08e5908777661 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Mon, 19 Jan 2026 17:36:07 +0530 Subject: [PATCH 12/14] [FIX] estate: apply code style suggestions - Replaced double quotes with single quotes - Updated XML formatting and styles - Reordered import statements alphabetically --- estate/models/estate_property.py | 10 ++++---- estate/models/estate_property_maintenance.py | 2 +- estate/models/estate_property_offer.py | 25 ++++++++------------ estate/models/estate_property_tag.py | 2 +- estate/models/etsate_property_type.py | 2 +- estate/views/res_users_views.xml | 2 +- estate_account/models/estate_property.py | 2 +- 7 files changed, 20 insertions(+), 25 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f6a4d667189..e914d98c86b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,11 +1,11 @@ from datetime import timedelta -from odoo import models, fields, api +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero -class Property(models.Model): +class EstateProperty(models.Model): _name = 'estate.property' _description = 'estate property details' _order = 'id desc' @@ -85,7 +85,7 @@ def _compute_best_price(self): def _onchange_garden(self): if self.garden: self.garden_area = 10 - self.garden_orientation = "north" + self.garden_orientation = 'north' else: self.garden_area = None self.garden_orientation = None @@ -105,13 +105,13 @@ def action_property_sold(self): for record in self.property_maintainance_ids: if record.status != 'done': raise UserError("Maintenance Request are still pending.") - self.state = "sold" + self.state = 'sold' def action_property_cancel(self): if self.state == 'sold': raise UserError("Sold property cannot be Cancelled.") else: - self.state = "cancelled" + self.state = 'cancelled' @api.depends('property_maintainance_ids.cost') def _compute_total_maintenance_cost(self): diff --git a/estate/models/estate_property_maintenance.py b/estate/models/estate_property_maintenance.py index 8d0a546e395..a2dd8bdecfe 100644 --- a/estate/models/estate_property_maintenance.py +++ b/estate/models/estate_property_maintenance.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import api, fields, models from odoo.exceptions import UserError from odoo.tools.float_utils import float_is_zero diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 21ba3fad733..bebf7af46e2 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,6 @@ from datetime import timedelta -from odoo import models, fields, api +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare @@ -36,27 +36,22 @@ class PropertyOffer(models.Model): @api.depends('validity') def _compute_date_deadline(self): for record in self: - if record.create_date: - record.date_deadline = record.create_date + \ - timedelta(days=record.validity) - else: - record.date_deadline = fields.Date.today() + timedelta(days=record.validity) + record.date_deadline = (record.create_date or fields.Date.today()) + \ + timedelta(days=record.validity) def _inverse_date_deadline(self): for record in self: - if record.date_deadline: - record.validity = ( - record.date_deadline - record.create_date.date() - ).days - else: - record.validity = (record.date_deadline - fields.Date.today()) + record.validity = ( + record.date_deadline - + (record.create_date.date() or fields.Date.today()) + ).days def action_accepted(self): accepect_records = self.property_id.offer_ids.filtered( lambda o: o.status == 'accepted') if accepect_records: raise UserError("Only one offer can be accepted.") - self.status = "accepted" + self.status = 'accepted' self.property_id.partner_id = self.partner_id self.property_id.selling_price = self.price self.property_id.state = 'offer_accepted' @@ -66,7 +61,7 @@ def action_refused(self): self.property_id.partner_id = None self.property_id.selling_price = None self.property_id.state = 'offer_received' - self.status = "refused" + self.status = 'refused' @api.ondelete(at_uninstall=False) def _ondelete_offer(self): @@ -80,7 +75,7 @@ def create(self, vals): price = val.get('price') property_id = val.get('property_id') property = self.env['estate.property'].browse(property_id) - if property.state == "new": + if property.state == 'new': property.best_price = price elif float_compare(price, property.best_price, precision_rounding=0.01) < 0: raise UserError( diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 0ba93071667..53aad86f51d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import fields, models class PropertyTag(models.Model): diff --git a/estate/models/etsate_property_type.py b/estate/models/etsate_property_type.py index fff42027d7a..ef984d2d1fb 100644 --- a/estate/models/etsate_property_type.py +++ b/estate/models/etsate_property_type.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import api, fields, models class PropertyType(models.Model): diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index a73b7934e09..5a37af94f06 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -22,4 +22,4 @@
-
\ No newline at end of file + diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 22afeae115f..5a67df5eac0 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, Command +from odoo import Command, models class EstateProperty(models.Model): From 96fbb34f5b5afee11adf935764d4617ca45111a9 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Wed, 21 Jan 2026 12:18:04 +0530 Subject: [PATCH 13/14] [ADD] estate: completed Chapter 14 and 15 - Added kanban view for estate.property. - Added key fields for quick overview. - Optimized aggregations using read_group. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_investor.py | 9 ++++++ estate/models/estate_property.py | 20 ++++++------ estate/models/res_users.py | 10 +++++- estate/security/ir.model.access.csv | 1 + estate/views/estate_investor_views.xml | 9 ++++++ estate/views/estate_menus.xml | 2 +- estate/views/estate_property_views.xml | 44 ++++++++++++++++++++++++-- estate/views/res_users_views.xml | 8 ++++- 10 files changed, 89 insertions(+), 16 deletions(-) create mode 100644 estate/models/estate_investor.py create mode 100644 estate/views/estate_investor_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ee6ff2a5cca..a20b55353e1 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,7 @@ 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', 'views/res_users_views.xml', + 'views/estate_investor_views.xml', 'views/estate_menus.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 465e23d68d5..8e1e543760c 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -4,3 +4,4 @@ from . import etsate_property_type from . import estate_property_maintenance from . import res_users +from . import estate_investor diff --git a/estate/models/estate_investor.py b/estate/models/estate_investor.py new file mode 100644 index 00000000000..08e786dfd6e --- /dev/null +++ b/estate/models/estate_investor.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class EstateInvestor(models.Model): + _name = "estate.investor" + _description = "investor details" + _rec_name = 'id' + + name = fields.Many2one('res.partner') diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e914d98c86b..9671c56cd14 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -59,7 +59,7 @@ class EstateProperty(models.Model): 'estate.property.maintenance', 'property_id') total_maintenance_cost = fields.Float( compute='_compute_total_maintenance_cost') - + investor = fields.Many2one('estate.investor') _check_expected_price = models.Constraint( 'CHECK(expected_price > 0)', "The Expected price cannot be negative or zero." @@ -71,15 +71,15 @@ class EstateProperty(models.Model): @api.depends('garden_area', 'living_area') def _compute_total_area(self): - self.total_area = self.living_area + self.garden_area + for record in self: + record.total_area = record.living_area + record.garden_area @api.depends('offer_ids.price') def _compute_best_price(self): + best_price = dict(self.env['estate.property.offer']._read_group(domain=[ + ('property_id', 'in', self.ids)], aggregates=['price:max'], groupby=['property_id'])) for record in self: - if record.offer_ids.mapped('price'): - record.best_price = max(record.offer_ids.mapped('price')) - else: - record.best_price = None + record.best_price = best_price.get(record, 0.0) @api.onchange('garden') def _onchange_garden(self): @@ -115,12 +115,10 @@ def action_property_cancel(self): @api.depends('property_maintainance_ids.cost') def _compute_total_maintenance_cost(self): + maintenace_cost = dict(self.env['estate.property.maintenance']._read_group(domain=[( + 'property_id', 'in', self.ids)], aggregates=['cost:sum'], groupby=['property_id'])) for record in self: - if record.property_maintainance_ids: - record.total_maintenance_cost = sum( - record.property_maintainance_ids.mapped('cost')) - else: - record.total_maintenance_cost = 0.00 + record.total_maintenance_cost = maintenace_cost.get(record, 0.0) @api.ondelete(at_uninstall=False) def _unlink_property(self): diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 4fa1dc736ff..a320f45e780 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -1,7 +1,15 @@ -from odoo import fields, models +from odoo import api, fields, models class ResUsers(models.Model): _inherit = 'res.users' property_ids = fields.One2many('estate.property', 'user_id') + unsold_value = fields.Float(compute='_compute_unsold_value') + + @api.depends('property_ids.expected_price', 'property_ids.state') + def _compute_unsold_value(self): + sum_unsold_value = dict(self.env['estate.property']._read_group(domain=[('state', '!=', 'sold'), ( + 'user_id', 'in', self.ids)], aggregates=['expected_price:sum'], groupby=['user_id'])) + for record in self: + record.unsold_value = sum_unsold_value.get(record) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 9c9bf8a21a2..3e4093797f0 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -4,3 +4,4 @@ model_estate_type,model_estate_type,model_estate_property_type,base.group_user,1 model_estate_tag,model_estate_tag,model_estate_property_tag,base.group_user,1,1,1,1 model_estate_offer,model_estate_offer,model_estate_property_offer,base.group_user,1,1,1,1 model_estate_maintenance,model_estate_maintenance,model_estate_property_maintenance,base.group_user,1,1,1,1 +estate.access_estate_investor,access_estate_investor,estate.model_estate_investor,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_investor_views.xml b/estate/views/estate_investor_views.xml new file mode 100644 index 00000000000..f11312b21ca --- /dev/null +++ b/estate/views/estate_investor_views.xml @@ -0,0 +1,9 @@ + + + + Investor + estate.investor + list,form + + + diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 683d0504aad..462370c7773 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -3,13 +3,13 @@ - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5224c945771..0b2902f47bd 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -49,6 +49,7 @@ + @@ -125,11 +126,50 @@ + + estate.property.view.kanban + estate.property + + + + + +
+

+ +

+
+
+ Expected Price: + +
+
+ Best Price: + +
+
+ Selling Price: + +
+
+ +
+
+
+ +
+
+
+
+
+
+
+ Properties estate.property - list,form - {'search_default_best_price': 1} + list,form,kanban + {'search_default_best_price': 0} diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index 5a37af94f06..3dd8c4f0269 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -4,9 +4,12 @@ res.users + + + - + @@ -18,6 +21,9 @@ + + + From 3e60be33c55a1cfd69b2b170bdb36b36a02edcc3 Mon Sep 17 00:00:00 2001 From: pkhu-odoo Date: Fri, 23 Jan 2026 12:06:10 +0530 Subject: [PATCH 14/14] [ADD] estate: OWL basics and estate enhancements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Completed exercises 1–4 from OWL Components tutorial - Added “Accept Best Price” button in property form view. - Implemented chatter in the property form view. - Added _() translations for error messages. --- awesome_owl/static/src/Card/card.js | 9 +++++ awesome_owl/static/src/Card/card.xml | 15 ++++++++ awesome_owl/static/src/Counter/counter.js | 13 +++++++ awesome_owl/static/src/Counter/counter.xml | 11 ++++++ awesome_owl/static/src/playground.js | 6 +++- awesome_owl/static/src/playground.xml | 11 +++--- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 38 +++++++++++--------- estate/models/estate_property_maintenance.py | 4 +-- estate/models/estate_property_offer.py | 14 ++++---- estate/views/estate_property_type_views.xml | 2 +- estate/views/estate_property_views.xml | 13 ++++--- 12 files changed, 96 insertions(+), 42 deletions(-) create mode 100644 awesome_owl/static/src/Card/card.js create mode 100644 awesome_owl/static/src/Card/card.xml create mode 100644 awesome_owl/static/src/Counter/counter.js create mode 100644 awesome_owl/static/src/Counter/counter.xml diff --git a/awesome_owl/static/src/Card/card.js b/awesome_owl/static/src/Card/card.js new file mode 100644 index 00000000000..f934fbf140f --- /dev/null +++ b/awesome_owl/static/src/Card/card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: String, + content: String, + }; +} diff --git a/awesome_owl/static/src/Card/card.xml b/awesome_owl/static/src/Card/card.xml new file mode 100644 index 00000000000..3895a39ab8b --- /dev/null +++ b/awesome_owl/static/src/Card/card.xml @@ -0,0 +1,15 @@ + + + +
+
+
+ +
+

+ +

+
+
+
+
diff --git a/awesome_owl/static/src/Counter/counter.js b/awesome_owl/static/src/Counter/counter.js new file mode 100644 index 00000000000..12be97c5229 --- /dev/null +++ b/awesome_owl/static/src/Counter/counter.js @@ -0,0 +1,13 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = {}; + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + } +} diff --git a/awesome_owl/static/src/Counter/counter.xml b/awesome_owl/static/src/Counter/counter.xml new file mode 100644 index 00000000000..748521c23b2 --- /dev/null +++ b/awesome_owl/static/src/Counter/counter.xml @@ -0,0 +1,11 @@ + + + +
+ hello world +

Counter: +

+ +
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..9d8b6a2e951 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,9 @@ -import { Component } from "@odoo/owl"; +import { Component, markup } from "@odoo/owl"; +import { Counter } from "./Counter/counter"; +import { Card } from "./Card/card"; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = {Counter, Card} + value1 = markup("
This is the first card content
"); } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..fc193fa23b3 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,9 @@ - + - -
- hello world -
+ + + +
-
diff --git a/estate/__manifest__.py b/estate/__manifest__.py index a20b55353e1..daece280d85 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,7 @@ 'name': "estate", 'author': "pkhu", 'license': "LGPL-3", - 'depends': ['base'], + 'depends': ['base', 'mail'], 'application': True, 'data': [ 'security/ir.model.access.csv', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9671c56cd14..ed792639cf1 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,6 @@ from datetime import timedelta -from odoo import api, fields, models +from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero @@ -9,6 +9,7 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'estate property details' _order = 'id desc' + _inherit = 'mail.thread' name = fields.Char(required=True) description = fields.Text() @@ -47,7 +48,8 @@ class EstateProperty(models.Model): ) property_type_id = fields.Many2one( 'estate.property.type', string="Property Type") - user_id = fields.Many2one('res.users', string="Salesperson") + user_id = fields.Many2one( + 'res.users', string="Salesperson", default=lambda self: self.env.user) partner_id = fields.Many2one('res.partner', string="Buyer", readonly=True) tag_ids = fields.Many2many('estate.property.tag', string="Tags") offer_ids = fields.One2many( @@ -94,24 +96,25 @@ def _onchange_garden(self): def _constraint_selling_price(self): if float_is_zero(self.selling_price, precision_rounding=0.01): return - if float_compare(self.selling_price, self.expected_price * 0.9, precision_rounding=0.01) < 0: - raise ValidationError( - "Selling price cannot be lower than 90% of the expected price.") + elif float_compare(self.selling_price, self.expected_price * 0.9, precision_rounding=0.01) < 0: + raise ValidationError(_( + "Selling price cannot be lower than 90% of the expected price.")) def action_property_sold(self): - if self.state == 'cancelled': - raise UserError("Cancelled property cannot be sold.") - else: - for record in self.property_maintainance_ids: - if record.status != 'done': - raise UserError("Maintenance Request are still pending.") - self.state = 'sold' + if self.state != 'offer_accepted': + raise UserError(_("Atleast one offer should be accepted.")) + for record in self.property_maintainance_ids: + if record.status != 'done': + raise UserError(_("Maintenance Request are still pending.")) + self.state = 'sold' def action_property_cancel(self): - if self.state == 'sold': - raise UserError("Sold property cannot be Cancelled.") - else: - self.state = 'cancelled' + self.state = 'cancelled' + + def action_accept_best_offer(self): + data = self.env['estate.property.offer'].search( + domain=[('property_id', 'in', self.ids), ('price', '=', self.best_price)], limit=1) + data.action_accepted() @api.depends('property_maintainance_ids.cost') def _compute_total_maintenance_cost(self): @@ -123,4 +126,5 @@ def _compute_total_maintenance_cost(self): @api.ondelete(at_uninstall=False) def _unlink_property(self): if self.state not in ['new', 'cancelled']: - raise UserError("Only new and cancelled property can be deleted.") + raise UserError( + _("Only new and cancelled property can be deleted.")) diff --git a/estate/models/estate_property_maintenance.py b/estate/models/estate_property_maintenance.py index a2dd8bdecfe..1e0705d2aaf 100644 --- a/estate/models/estate_property_maintenance.py +++ b/estate/models/estate_property_maintenance.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import _, api, fields, models from odoo.exceptions import UserError from odoo.tools.float_utils import float_is_zero @@ -17,4 +17,4 @@ class PropertyMantainance(models.Model): def _onchange_status(self): for record in self: if record.status == 'approved' and float_is_zero(record.cost, precision_rounding=0.01): - raise UserError("Cost must be greater than zero.") + raise UserError(_("Cost must be greater than zero.")) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index bebf7af46e2..3ccb54b2182 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,6 @@ from datetime import timedelta -from odoo import api, fields, models +from odoo import _, api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare @@ -47,10 +47,10 @@ def _inverse_date_deadline(self): ).days def action_accepted(self): - accepect_records = self.property_id.offer_ids.filtered( - lambda o: o.status == 'accepted') - if accepect_records: - raise UserError("Only one offer can be accepted.") + offers = self.property_id.offer_ids.filtered( + lambda a: a.id != self.id) + for offer in offers: + offer.status = 'refused' self.status = 'accepted' self.property_id.partner_id = self.partner_id self.property_id.selling_price = self.price @@ -67,7 +67,7 @@ def action_refused(self): def _ondelete_offer(self): for records in self: if records.status == 'accepted': - raise ValidationError("Accepted offer cannot be deleted.") + raise ValidationError(_("Accepted offer cannot be deleted.")) @api.model def create(self, vals): @@ -79,7 +79,7 @@ def create(self, vals): property.best_price = price elif float_compare(price, property.best_price, precision_rounding=0.01) < 0: raise UserError( - f"Price should be greater than {property.best_price}") + _("Price should be greater than %s", property.best_price)) else: property.best_price = price if property and property.state == 'new': diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index fb5888b50f0..b64f2b9df44 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -4,7 +4,7 @@ estate.property.type.view.list estate.property.type - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0b2902f47bd..479e75648ff 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -28,6 +28,7 @@
@@ -38,13 +39,10 @@
- - -

- -

-
-
+

+

@@ -100,6 +98,7 @@ +