From 71296e371360e60a5b9ff97d95b730106a6e7cc0 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Wed, 31 Dec 2025 15:47:16 +0530 Subject: [PATCH 01/20] [ADD] estate: initialize module structure and model - Real Estate module created - Basic structure and required fields added - CHAPTER 1, 2, and 3 --- estate/__init__.py | 1 + estate/__manifest__.py | 9 +++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 23 +++++++++++++++++++++++ 4 files changed, 34 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 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..3088bfc7c0e --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,9 @@ +{ + 'name': 'Real Estate', + 'version': '1.0', + 'category': 'Real Estate', + 'description':'Manage real estate properties', + 'depends': ['base'], + 'application': True, + 'installable': True, +} \ 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..5869466661d --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,23 @@ +from odoo import fields, models + +class EstateProperty(models.Model): + _name = 'estate.property' + _description = 'Real Estate Property' + + # Each field becomes a column in PostgreSQL table + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + string='Direction', + selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West')], + ) \ No newline at end of file From 3665d347d4916815a0bec7e4d385ddea09e95c20 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Thu, 1 Jan 2026 10:33:51 +0530 Subject: [PATCH 02/20] [ADD] estate: add security rules and fix style - Added basic security access for estate records - Fixed missing whitespace and formatting issues - Covers CHAPTER 4 --- estate/__init__.py | 2 +- estate/__manifest__.py | 10 ++++++++-- estate/models/estate_property.py | 12 ++++++++---- estate/security/ir.model.access.csv | 2 ++ 4 files changed, 19 insertions(+), 7 deletions(-) create mode 100644 estate/security/ir.model.access.csv 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 3088bfc7c0e..c541d0ffc5c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,8 +2,14 @@ 'name': 'Real Estate', 'version': '1.0', 'category': 'Real Estate', - 'description':'Manage real estate properties', + 'summary': 'Manage real estate properties', + 'description': 'This module allows managing properties.', 'depends': ['base'], + 'author': 'jakan', + 'license': 'LGPL-3', 'application': True, 'installable': True, -} \ No newline at end of file + 'data': [ + 'security/ir.model.access.csv' + ] +} diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 5869466661d..6a7b1e2e28d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,7 +1,8 @@ from odoo import fields, models + class EstateProperty(models.Model): - _name = 'estate.property' + _name = 'estate.property' _description = 'Real Estate Property' # Each field becomes a column in PostgreSQL table @@ -18,6 +19,9 @@ class EstateProperty(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Direction', - selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West')], - ) \ No newline at end of file + string="Direction", + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West")]) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..32389642d4f --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 From d8791e0606e6de01b2bb4cee0d6787ceccd22bab Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Thu, 1 Jan 2026 18:48:11 +0530 Subject: [PATCH 03/20] [ADD] estate: add basic user interface - Added form and list views for estate records - Added menu and action to access the module - Covers CHAPTER 5 --- estate/__manifest__.py | 4 +++- estate/models/estate_property.py | 26 +++++++++++++++++++++++--- estate/views/estate_menus.xml | 18 ++++++++++++++++++ estate/views/estate_property_views.xml | 8 ++++++++ 4 files changed, 52 insertions(+), 4 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 c541d0ffc5c..0f1a69248b6 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,8 @@ 'application': True, 'installable': True, 'data': [ - 'security/ir.model.access.csv' + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ] } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 6a7b1e2e28d..e9ad5914d60 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import fields, models +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): @@ -9,10 +10,16 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date( + default=lambda self: fields.Date.today() + relativedelta(months=3), + copy=False + ) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float( + readonly=True, + copy=False + ) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -25,3 +32,16 @@ class EstateProperty(models.Model): ("south", "South"), ("east", "East"), ("west", "West")]) + active = fields.Boolean(default=True) + state = fields.Selection( + [ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + required=True, + copy=False, + default="new", + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..3681323382a --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..7534b527918 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Property + estate.property + list,form + + From 378ed7cc81cb27b736414869fda432d4d73dee38 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Fri, 2 Jan 2026 15:38:45 +0530 Subject: [PATCH 04/20] [IMP] estate: create basic views and search - Added list, form, and search views - Added domains and group by options - Covers CHAPTER 6 --- estate/views/estate_property_views.xml | 73 ++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7534b527918..31551e81672 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,4 +5,77 @@ estate.property list,form + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + From f1557021333c22358b6c6c9b9aaf72bb4922c5f1 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Mon, 5 Jan 2026 18:45:37 +0530 Subject: [PATCH 05/20] [IMP] estate: add many2one model relation - Added Many2one relation between models - Improves data linking between records - Covers CHAPTER 7 --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 7 +++-- estate/models/estate_property_type.py | 8 ++++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_menus.xml | 23 ++++++---------- estate/views/estate_property_type_views.xml | 30 +++++++++++++++++++++ estate/views/estate_property_views.xml | 20 +++++++++----- 8 files changed, 68 insertions(+), 23 deletions(-) create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 0f1a69248b6..3fa5cd406f3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,7 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', 'views/estate_menus.xml', ] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..40092a2d810 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,2 @@ from . import estate_property +from . import estate_property_type diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e9ad5914d60..dd499b0e6eb 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import fields, models from dateutil.relativedelta import relativedelta +from odoo import fields, models class EstateProperty(models.Model): @@ -7,7 +7,7 @@ class EstateProperty(models.Model): _description = 'Real Estate Property' # Each field becomes a column in PostgreSQL table - name = fields.Char(required=True) + name = fields.Char(required=True, default="Unknown") description = fields.Text() postcode = fields.Char() date_availability = fields.Date( @@ -45,3 +45,6 @@ class EstateProperty(models.Model): copy=False, default="new", ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + seller_id = fields.Many2one("res.users", string="Seller", default=lambda self: self.env.user) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..8e04ff22035 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Estate Property Type' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 32389642d4f..11da225066f 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 3681323382a..116ef30b0c1 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,18 +1,11 @@ - - - + + + + + + + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..317fbb4dad7 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,30 @@ + + + + Property Types + estate.property.type + list,form + + + + estate.property.type.view.list + estate.property.type + + + + + + + + + estate.property.type.view.form + estate.property.type + +
+ +

+
+
+
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 31551e81672..46a798abe00 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,12 +6,13 @@ list,form - - estate.property.list + + estate.property.view.list estate.property + @@ -22,8 +23,8 @@ - - estate.property.form + + estate.property.view.form estate.property
@@ -33,6 +34,7 @@ + @@ -55,14 +57,20 @@ + + + + + +
- - estate.property.search + + estate.property.view.search estate.property From 1c69bbe4999c9785ed6bb61817f0e7bbc606c81d Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Tue, 6 Jan 2026 15:16:14 +0530 Subject: [PATCH 06/20] [IMP] estate: add one2many and many2many relations - Added One2many and Many2many relations between models - Improves linking and management of related records - Covers CHAPTER 7 --- estate/__manifest__.py | 2 ++ estate/models/__init__.py | 2 ++ estate/models/estate_property.py | 2 ++ estate/models/estate_property_offer.py | 18 ++++++++++++ estate/models/estate_property_tag.py | 8 ++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 1 + estate/views/estate_property_offer_views.xml | 30 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 30 ++++++++++++++++++++ estate/views/estate_property_views.xml | 10 +++++++ 10 files changed, 105 insertions(+) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3fa5cd406f3..1c321a129e1 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,6 +13,8 @@ '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', ] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 40092a2d810..09b2099fe84 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,2 +1,4 @@ from . import estate_property from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index dd499b0e6eb..307bcb811d3 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -48,3 +48,5 @@ class EstateProperty(models.Model): property_type_id = fields.Many2one("estate.property.type", string="Property Type") buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) seller_id = fields.Many2one("res.users", string="Seller", default=lambda self: self.env.user) + tag_ids = fields.Many2many("estate.property.tag", string="Property Tags") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Property Offers") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..43a68bcb60e --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,18 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = 'Estate Property Offer' + + price = fields.Float(string="Price") + status = fields.Selection( + [ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + string="Status", + copy=False + ) + partner_id = fields.Many2one('res.partner', string="Partner", required=True) + property_id = fields.Many2one('estate.property', string="Property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..fef8d256bae --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'Estate Property Tag' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 11da225066f..89f97c50842 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,3 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_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 116ef30b0c1..7be145724ff 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -6,6 +6,7 @@ + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..97307c56f46 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,30 @@ + + + + estate.property.offer.view.list + estate.property.offer + + + + + + + + + + + estate.property.offer.view.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..2861aed9239 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,30 @@ + + + + Property Tags + estate.property.tag + list,form + + + + estate.property.tag.view.list + estate.property.tag + + + + + + + + + estate.property.tag.view.form + estate.property.tag + +
+ +

+
+
+
+
+
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 46a798abe00..20cd9575008 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -32,6 +32,7 @@

+ @@ -57,6 +58,15 @@ + + + + + + + + + From 9131c01909cd4f4f985e008fb3f363316c1ce4d3 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Wed, 7 Jan 2026 11:42:58 +0530 Subject: [PATCH 07/20] [IMP] estate: add computed fields and onchanges - Added computed fields for automatic values - Added onchange methods to update fields - Covers CHAPTER 8 --- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 26 +++++++++++++++++- estate/models/estate_property_offer.py | 28 +++++++++++++++++++- estate/views/estate_property_offer_views.xml | 4 +++ estate/views/estate_property_views.xml | 6 ++++- 5 files changed, 62 insertions(+), 4 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe84..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,4 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 307bcb811d3..15b70615e18 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import fields, models, api class EstateProperty(models.Model): @@ -50,3 +50,27 @@ class EstateProperty(models.Model): seller_id = fields.Many2one("res.users", string="Seller", default=lambda self: self.env.user) tag_ids = fields.Many2many("estate.property.tag", string="Property Tags") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Property Offers") + total_area = fields.Float(string="Total Area", compute="_compute_total_area") + best_price = fields.Float(string="Best Offer", compute="_compute_best_price") + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for property in self: + property.total_area = property.living_area + property.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for property in self: + if property.offer_ids: + property.best_price = max(property.offer_ids.mapped('price')) + else: + property.best_price = 0 + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 43a68bcb60e..7fa056006bc 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import fields, models +from dateutil.relativedelta import relativedelta +from odoo import fields, models, api class EstatePropertyOffer(models.Model): @@ -16,3 +17,28 @@ class EstatePropertyOffer(models.Model): ) 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_date_deadline", + inverse="_inverse_date_deadline", + store=True + ) + + @api.depends("validity", "create_date") + def _compute_date_deadline(self): + for offer in self: + if offer.create_date: + offer.date_deadline = ( + offer.create_date.date() + + relativedelta(days=offer.validity) + ) + else: + offer.date_deadline = fields.Date.today() + relativedelta(days=offer.validity) + + def _inverse_date_deadline(self): + for offer in self: + if offer.create_date and offer.date_deadline: + offer.validity = ( + offer.date_deadline + - offer.create_date.date() + ).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 97307c56f46..5f714d8b04d 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,8 @@ + + @@ -21,6 +23,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 20cd9575008..c0072e25f0f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -41,7 +41,8 @@ - + + @@ -56,6 +57,7 @@ + @@ -63,6 +65,8 @@ + + From 2784e60f3665ae2b5aaa43a6b65ffd24c9c6b926 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Wed, 7 Jan 2026 18:30:16 +0530 Subject: [PATCH 08/20] [IMP] estate: implement object-based actions - Created Sell, Cancel, Accept, and Refuse buttons - Buttons call related methods on the estate models - Buyer and selling price are set when an offer is accepted - Covers CHAPTER 9 --- estate/models/estate_property.py | 15 +++++++++++++++ estate/models/estate_property_offer.py | 19 +++++++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 11 ++++------- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 15b70615e18..dec99a2493b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError from odoo import fields, models, api @@ -74,3 +75,17 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + def action_sold(self): + for property in self: + if property.state == 'cancelled': + raise UserError("Sold property cannot be cancelled") + property.state = 'sold' + return True + + def action_cancel(self): + for property in self: + if property.state == 'sold': + raise UserError("Cancelled property cannot be sold") + property.state = 'cancelled' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7fa056006bc..9ac29cf51e5 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError from odoo import fields, models, api @@ -42,3 +43,21 @@ def _inverse_date_deadline(self): offer.date_deadline - offer.create_date.date() ).days + + def action_accept(self): + for offer in self: + if offer.property_id.buyer_id: + raise UserError("Property already accepted") + + other_offer = offer.property_id.offer_ids - offer + other_offer.write({'status': 'refused'}) + + offer.status = "accepted" + offer.property_id.buyer_id = offer.partner_id + offer.property_id.selling_price = offer.price + return True + + def action_refuse(self): + for offer in self: + offer.status = "refused" + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 5f714d8b04d..72e3e8708bf 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,6 +9,8 @@ + +

diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index bddde23dc59..5732934360c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,13 +4,18 @@ Property estate.property list,form + {'search_default_available_properties': 1} estate.property.view.list estate.property - + @@ -18,7 +23,7 @@ - + @@ -33,16 +38,16 @@
-

- + - + @@ -62,14 +67,14 @@ - - + + - + @@ -97,7 +102,7 @@ - + From 54f03a6dbac5e43ba9e6e274ab39e2d6d8890707 Mon Sep 17 00:00:00 2001 From: jakan-odoo Date: Fri, 16 Jan 2026 17:53:21 +0530 Subject: [PATCH 14/20] [IMP] estate: apply python, model, and view inheritance - Used Python inheritance to extend existing logic - Applied model inheritance to add fields and behavior - Used view inheritance to update and extend UI - Covers CHAPTER 12 --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 11 ++++++-- estate/models/estate_property_offer.py | 22 ++++++++++++++- estate/models/res_users.py | 11 ++++++++ estate/views/estate_property_offer_views.xml | 2 +- estate/views/estate_property_views.xml | 13 +++++---- estate/views/res_users_views.xml | 28 ++++++++++++++++++++ 8 files changed, 78 insertions(+), 11 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 d32a8635e8f..86e513f1314 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -16,6 +16,7 @@ "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", "views/estate_property_maintenance_views.xml", + 'views/res_users_views.xml', "views/estate_menus.xml", ] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 86dd6b9d6ee..9e6c1ddc62f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,3 +3,4 @@ from . import estate_property_tag from . import estate_property_offer from . import estate_property_maintenance +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 74ae4a9911b..1f5745722b2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from dateutil.relativedelta import relativedelta -from odoo import fields, models, api + +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero @@ -53,7 +54,7 @@ class EstateProperty(models.Model): seller_id = fields.Many2one("res.users", string="Seller", default=lambda self: self.env.user) tag_ids = fields.Many2many("estate.property.tag", string="Property Tags") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Property Offers") - total_area = fields.Float(string="Total Area", compute="_compute_total_area") + total_area = fields.Float(string="Total Area", compute="_compute_total_area", store=True) best_price = fields.Float(string="Best Offer", compute="_compute_best_price") maintenance_ids = fields.One2many("estate.property.maintenance", "property_id") total_cost = fields.Float(string="Total Cost", compute="_compute_total_cost") @@ -125,3 +126,9 @@ def _check_selling_price(self): def _compute_total_cost(self): for maintenance in self: maintenance.total_cost = sum(maintenance.maintenance_ids.mapped('cost')) + + @api.ondelete(at_uninstall=False) + def _check_property_deletion(self): + for property in self: + if property.state not in ('new', 'cancelled'): + raise UserError("You can only delete properties in New or Cancelled state") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 3d11454ca2a..b020a41a282 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from dateutil.relativedelta import relativedelta + +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError -from odoo import fields, models, api class EstatePropertyOffer(models.Model): @@ -57,6 +58,8 @@ def action_accept(self): offer.status = "accepted" offer.property_id.buyer_id = offer.partner_id offer.property_id.selling_price = offer.price + offer.property_id.state = "sold" + offer.property_id.active = False return True def action_refuse(self): @@ -74,3 +77,20 @@ def _check_property_state(self): for offer in self: if offer.property_id.state in ('sold', 'cancelled'): raise ValidationError("You cannot add an offer on a Sold or Cancelled property") + + @api.model + def create(self, vals_list): + for vals in vals_list: + property_id = vals.get("property_id") + price = vals.get("price") + property_rec = self.env["estate.property"].browse(property_id) + + # Prevent lower offer + existing_prices = property_rec.offer_ids.mapped("price") + if existing_prices and price < max(existing_prices): + raise UserError("You cannot create an offer lower than an existing offer") + + # Set property state + 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..a8020186469 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,11 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + 'estate.property', + 'seller_id', + string="Assigned Properties", + ) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index c480922b932..c7950ac6dd5 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -18,7 +18,7 @@ + + + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..19715362956 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,13 @@ -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 }; + + setup() { + this.htmlContent = markup("
some content
"); + this.rawString = "
some content
"; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..5d83b2c1c5d 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,13 @@ -
hello world + +
+ + +
-
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8d975b6f62c..9d811390979 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -12,7 +12,7 @@ class EstateProperty(models.Model): _order = 'id desc' # Each field becomes a column in PostgreSQL table - name = fields.Char(required=True, default="Unknown") + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date( @@ -133,3 +133,13 @@ def action_cancel(self): raise UserError("Cancelled property cannot be sold") property.state = 'cancelled' return True + + def offer_accepted(self): + for record in self: + if not record.offer_ids: + raise UserError("There are no offers to accept") + + best_offer = max(record.offer_ids, key=lambda p:p.price) + best_offer.action_accept() + + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4e92a7933e1..ac3eb07f928 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -89,7 +89,7 @@ def action_accept(self): property_rec.write({ 'buyer_id': offer.partner_id.id, 'selling_price': offer.price, - 'active': False, + 'state': 'offer_accepted', }) return True diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 04084dcbfb1..d8d7613fd75 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -10,7 +10,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 45cc5432307..ac04d88a765 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -37,10 +37,11 @@