From ad01e4c12d5cfd78acc78048481d39a1040d0dad Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Thu, 5 Feb 2026 17:49:31 +0530 Subject: [PATCH 01/10] [IMP] estate: add security access for estate.property Chapter 4: Security - A Brief Introduction This commit adds proper access control for the `estate.property` model by introducing an entry in `ir.model.access.csv`. --- .gitignore | 4 ++++ estate/__init__.py | 1 + estate/__manifest__.py | 8 ++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 27 +++++++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ 6 files changed, 43 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 diff --git a/.gitignore b/.gitignore index b6e47617de1..810aac7ee2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# custom ignore +custom_module/ + + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..775e1fbd813 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,8 @@ +{ + "name": "Estate", + "application": True, + "description": "Specific Real Estate Module", + "author": "Maram-Odoo", + "license": "LGPL-3", + "depends": ["base"], +} 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..85c4c815616 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property Management Module" + + name = fields.Char(string="Name", required=True) + postcode = fields.Char(string="Post Code") + date_availability = fields.Date(string="Availability Date") + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bed Rooms") + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area") + garden_orientation = fields.Selection( + string="Garden Orientation", + 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..93b58f31333 --- /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_user,access.estate.property.user,model_estate_property,base.group_user,1,1,1,1 From b9fcefbe1e6b8e612442c2305a5bccc30c4a76dd Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Fri, 6 Feb 2026 13:00:37 +0530 Subject: [PATCH 02/10] [IMP] estate: views,menu and some relational fields are added Chapter 5 and 7:UI to play and Relations Between Models added window action and menuitem for estate_property to make visible app in apps section added form view and list view because the default view is never acceptable for a business application relational field are added like property offer, property tag,property tag and their model and views are created and added in security file. --- estate/__manifest__.py | 11 ++- estate/models/__init__.py | 3 + estate/models/estate_property.py | 33 ++++++- estate/models/estate_property_offer.py | 18 ++++ estate/models/estate_property_tag.py | 8 ++ estate/models/estate_property_type.py | 8 ++ estate/security/ir.model.access.csv | 5 +- estate/views/estate_menus.xml | 14 +++ estate/views/estate_property_offer_views.xml | 29 ++++++ estate/views/estate_property_tag_views.xml | 30 ++++++ estate/views/estate_property_type_views.xml | 30 ++++++ estate/views/estate_property_views.xml | 96 ++++++++++++++++++++ 12 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_menus.xml 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 create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 775e1fbd813..cc5cd450276 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,16 @@ "name": "Estate", "application": True, "description": "Specific Real Estate Module", + "depends": ["base"], + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_property_tag_views.xml", + "views/estate_property_type_views.xml", + "views/estate_property_offer_views.xml", + "views/estate_menus.xml", + ], + "installable": True, "author": "Maram-Odoo", "license": "LGPL-3", - "depends": ["base"], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..4b57c1674fc 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_tag +from . import estate_property_type +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 85c4c815616..f8ea6b43964 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,16 +1,20 @@ from odoo import fields, models +from datetime import date class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Management Module" - name = fields.Char(string="Name", required=True) + name = fields.Char(string="Title", required=True) + description = fields.Text(string="Description") postcode = fields.Char(string="Post Code") - date_availability = fields.Date(string="Availability Date") + date_availability = fields.Date( + string="Availability From", default=date.today(), copy=False + ) expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price") - bedrooms = fields.Integer(string="Bed Rooms") + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer(string="Bed Rooms", default=2) living_area = fields.Integer(string="Living Area") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") @@ -25,3 +29,24 @@ class EstateProperty(models.Model): ("west", "West"), ], ) + state = fields.Selection( + string="Status", + default="new", + copy=False, + required=True, + selection=[ + ("new", "New"), + ("offer_received", "Offer Received"), + ("accepted", "Accepted"), + ("sold", "Sold"), + ("canceled", "Cancelled"), + ], + ) + active = fields.Boolean(string="Active", default=True) + tag_ids = fields.Many2many("estate.property.tag", string="Property Tag") + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + salesman_id = fields.Many2one( + "res.users", string="Salesman", default=lambda self: self.env.uid + ) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..9fcc086a0eb --- /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() + partner_id = fields.Many2one("res.partner", required=True, string="Partner") + property_id = fields.Many2one("estate.property", required=True, string="Property") + status = fields.Selection( + string="Status", + copy=False, + selection=[ + ("accepted", "Accepted"), + ("refuse", "Refused"), + ], + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..0ef4309e229 --- /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(string="Property Tags", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..eb88993ff70 --- /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 Types" + + name = fields.Char(string="Property Types", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 93b58f31333..7d3ba7eb3ac 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 -access_estate_property_user,access.estate.property.user,model_estate_property,base.group_user,1,1,1,1 +access_estate_property,estate.property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_offer,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 new file mode 100644 index 00000000000..86a321356cd --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..058a8415b5a --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,29 @@ + + + + estate.property.offer.view.list + estate.property.offer + + + + + + + + + + estate.property.offer.view.form + estate.property.offer + +
+ + + + + + + +
+
+
+
\ 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..f53daefdbf2 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,30 @@ + + + + estate.property.tag.view.list + estate.property.tag + + + + + + + + estate.property.tag.view.form + estate.property.tag + +
+ + + + + +
+
+
+ + Estate 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..e1e6f0c4da3 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,30 @@ + + + + estate.property.type.view.list + estate.property.type + + + + + + + + estate.property.type.view.form + estate.property.type + +
+ + + + + +
+
+
+ + Estate 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 new file mode 100644 index 00000000000..47a55ba562e --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,96 @@ + + + + estate.property.view.list + estate.property + + + + + + + + + + + + + + estate.property.view.form + estate.property + +
+ + +

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + Estate Property + estate.property + list,form + +
\ No newline at end of file From f35432efc903f5d41aa21c01dbebe68e5418b8eb Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Tue, 10 Feb 2026 17:28:40 +0530 Subject: [PATCH 03/10] [FIX] estate: fixed python date fucnion to odoo built in function Chapter 5: Finally, Some UI To Play With 1-previously at availability date field python built-in date.today function was added but its odoo has its own Date.today function so added it and fixed that 2-default search filter and default group by is introduced in estate property view --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 6 ++++-- estate/views/estate_property_type_views.xml | 2 +- estate/views/estate_property_views.xml | 9 ++++++--- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index cc5cd450276..9c2912218cd 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,7 @@ "name": "Estate", "application": True, "description": "Specific Real Estate Module", - "depends": ["base"], + "depends": ["base", "crm"], "data": [ "security/ir.model.access.csv", "views/estate_property_views.xml", diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f8ea6b43964..7dd3631aa15 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from odoo import fields, models -from datetime import date +from datetime import date, timedelta class EstateProperty(models.Model): @@ -10,7 +10,9 @@ class EstateProperty(models.Model): description = fields.Text(string="Description") postcode = fields.Char(string="Post Code") date_availability = fields.Date( - string="Availability From", default=date.today(), copy=False + string="Availability From", + default=lambda self: fields.Date.today() + timedelta(days=90), + copy=False, ) expected_price = fields.Float(string="Expected Price", required=True) selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index e1e6f0c4da3..f1ba6987908 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -13,7 +13,7 @@ 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 47a55ba562e..aa7d28426f6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -82,9 +82,8 @@ - - - + + @@ -92,5 +91,9 @@ Estate Property estate.property list,form + {'search_default_available':1} + + \ No newline at end of file From ee2c502c425de89b687076bba9fcf598d8cdfdda Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Tue, 10 Feb 2026 17:45:29 +0530 Subject: [PATCH 04/10] [FIX] estate: removed unnecessary import and depends Chapter 5: Finally, Some UI To Play With 1-removed unnecessary imports and depends(crm) --- estate/__manifest__.py | 2 +- estate/models/estate_property.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9c2912218cd..cc5cd450276 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,7 @@ "name": "Estate", "application": True, "description": "Specific Real Estate Module", - "depends": ["base", "crm"], + "depends": ["base"], "data": [ "security/ir.model.access.csv", "views/estate_property_views.xml", diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7dd3631aa15..53780b756de 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from odoo import fields, models -from datetime import date, timedelta +from datetime import timedelta class EstateProperty(models.Model): From e39017202e8cbdf4f19392e9362bf74f38d7b46d Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Wed, 11 Feb 2026 21:53:29 +0530 Subject: [PATCH 05/10] [IMP] estate: some computed fields and methods introdced and styling and filter improved Chapter 8: Computed Fields And Onchanges computed field like total area added and according method is added for calculating total area by adding living area and garden area. same best offer field added that automatically assign max offer price from all recived offer. some filter and group by added and styling and readability is improved. --- estate/models/estate_property.py | 18 +++++++++++- estate/models/estate_property_offer.py | 4 ++- estate/views/estate_menus.xml | 31 +++++++++++++++----- estate/views/estate_property_offer_views.xml | 10 +++++-- estate/views/estate_property_tag_views.xml | 6 +++- estate/views/estate_property_type_views.xml | 6 +++- estate/views/estate_property_views.xml | 22 ++++++++++++-- 7 files changed, 82 insertions(+), 15 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 53780b756de..2f8f6b5bd49 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import fields, models from datetime import timedelta +from odoo import fields, models, api class EstateProperty(models.Model): @@ -52,3 +52,19 @@ class EstateProperty(models.Model): ) buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + total_area = fields.Float(compute="_compute_total_area") + best_offer = fields.Float(compute="_compute_best_offer") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_offer(self): + for record in self: + if record.offer_ids: + prices = record.offer_ids.mapped("price") + record.best_offer = max(prices) + else: + record.best_offer = 0.0 diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9fcc086a0eb..8fa69bf0729 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -5,7 +5,7 @@ class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "Estate Property Offer" - price = fields.Float() + price = fields.Float(required=True, string="Offer Price") partner_id = fields.Many2one("res.partner", required=True, string="Partner") property_id = fields.Many2one("estate.property", required=True, string="Property") status = fields.Selection( @@ -16,3 +16,5 @@ class EstatePropertyOffer(models.Model): ("refuse", "Refused"), ], ) + validity = fields.Integer(string="Validity", default=7) + date_deadline = fields.Date(string="Deadline Date") diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 86a321356cd..b16d06afcaa 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,14 +1,31 @@ - - - + + + + + + + - - + + - + + - \ No newline at end of file + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 058a8415b5a..fd6b6479f40 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,5 +1,6 @@ + estate.property.offer.view.list estate.property.offer @@ -7,10 +8,12 @@ - + + + estate.property.offer.view.form estate.property.offer @@ -20,10 +23,13 @@ + +
- \ 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 f53daefdbf2..be8d175a9fa 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -1,5 +1,6 @@ + estate.property.tag.view.list estate.property.tag @@ -9,6 +10,7 @@ + estate.property.tag.view.form estate.property.tag @@ -22,9 +24,11 @@ + Estate 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 index f1ba6987908..c14bb8422df 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -1,5 +1,6 @@ + estate.property.type.view.list estate.property.type @@ -9,6 +10,7 @@ + estate.property.type.view.form estate.property.type @@ -22,9 +24,11 @@ + Estate 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 aa7d28426f6..c6f272390dc 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,6 @@ + estate.property.view.list estate.property @@ -15,11 +16,15 @@ + estate.property.view.form estate.property
+
+ +

@@ -36,6 +41,7 @@ + @@ -49,7 +55,7 @@ - + @@ -66,6 +72,7 @@ + estate.property.search estate.property @@ -81,12 +88,22 @@ + + + + + + + + Estate Property estate.property @@ -96,4 +113,5 @@ - \ No newline at end of file + + From b12a4f305ea7ea79f277bf8da30c7888fca850ec Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Fri, 13 Feb 2026 10:00:08 +0530 Subject: [PATCH 06/10] [IMP] estate: method implemented for inverse fields Chapter 8: Computed Fields And Onchanges inverse method implemented for computed field validity and date_deadline by help of timedelta and fields Date today method --- estate/models/estate_property.py | 1 - estate/models/estate_property_offer.py | 26 +++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2f8f6b5bd49..4ee5415a3e7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,7 +5,6 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Management Module" - name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Post Code") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8fa69bf0729..f50c32dc24f 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 datetime import timedelta +from odoo import fields, models, api class EstatePropertyOffer(models.Model): @@ -16,5 +17,24 @@ class EstatePropertyOffer(models.Model): ("refuse", "Refused"), ], ) - validity = fields.Integer(string="Validity", default=7) - date_deadline = fields.Date(string="Deadline Date") + validity = fields.Integer( + string="Validity", + default=7, + compute="_compute_validity", + inverse="_inverse_validity", + ) + date_deadline = fields.Date( + string="Deadline Date", + default=lambda self: fields.Date.today() + timedelta(days=7), + ) + + @api.depends("date_deadline") + def _compute_validity(self): + for record in self: + if record.date_deadline: + today = fields.Date.today() + record.validity = (record.date_deadline - today).days + + def _inverse_validity(self): + for record in self: + record.date_deadline = record.create_date + timedelta(days=record.validity) From 658f2c43911608d21155461c05e544295f7f806a Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Fri, 13 Feb 2026 16:28:57 +0530 Subject: [PATCH 07/10] [FIX] estate: compute and inverse method fixed by using correct date methods Chapter 8: Computed Fields And Onchanges fixed date_availability default value 3-months by relativedelta instead of time delta because timedelta only calculates days but we need months. fixed compute method and inverse method for date deadline by using create date or today date,because if it is only today date than it will again recompile if we change validity. onchange decorator introduce in garden field to auto filling value of garden area and garden Orientation. --- estate/models/estate_property.py | 13 ++++++-- estate/models/estate_property_offer.py | 43 +++++++++++++++++--------- estate/views/estate_property_views.xml | 2 +- 3 files changed, 41 insertions(+), 17 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4ee5415a3e7..a6b44c1e826 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from datetime import timedelta +from dateutil.relativedelta import relativedelta from odoo import fields, models, api @@ -10,7 +10,7 @@ class EstateProperty(models.Model): postcode = fields.Char(string="Post Code") date_availability = fields.Date( string="Availability From", - default=lambda self: fields.Date.today() + timedelta(days=90), + default=lambda self: fields.Date.today() + relativedelta(months=3), copy=False, ) expected_price = fields.Float(string="Expected Price", required=True) @@ -67,3 +67,12 @@ def _compute_best_offer(self): record.best_offer = max(prices) else: record.best_offer = 0.0 + + @api.onchange("garden") + def onchange_garden(self): + if self.garden: + self.garden_area = 1000 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = "" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index f50c32dc24f..d71433dab95 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,3 @@ -from datetime import timedelta from odoo import fields, models, api @@ -17,24 +16,40 @@ class EstatePropertyOffer(models.Model): ("refuse", "Refused"), ], ) + date_deadline = fields.Date( + string="Deadline Date", + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + store=True, + ) validity = fields.Integer( string="Validity", default=7, - compute="_compute_validity", - inverse="_inverse_validity", - ) - date_deadline = fields.Date( - string="Deadline Date", - default=lambda self: fields.Date.today() + timedelta(days=7), + store=True, ) - @api.depends("date_deadline") - def _compute_validity(self): + # alternative : create_date = record.create_date.date() + @api.depends("validity") + def _compute_date_deadline(self): for record in self: - if record.date_deadline: - today = fields.Date.today() - record.validity = (record.date_deadline - today).days + if record.validity: + create_date = ( + fields.Date.to_date(record.create_date) or fields.Date.today() + ) + record.date_deadline = fields.Date.add( + create_date, days=record.validity + ) - def _inverse_validity(self): + def _inverse_date_deadline(self): for record in self: - record.date_deadline = record.create_date + timedelta(days=record.validity) + if record.date_deadline: + create_date = ( + fields.Date.to_date(record.create_date) or fields.Date.today() + ) + record.validity = (record.date_deadline - create_date).days + + @api.onchange("date_deadline") + def _onchange_validity(self): + if self.date_deadline: + create_date = fields.Date.to_date(self.create_date) or fields.Date.today() + self.validity = (self.date_deadline - create_date).days diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c6f272390dc..0d8017bf2b5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -55,7 +55,7 @@ - + From d058ad4c4c6e69a402ad693e5ddda72dab5bac97 Mon Sep 17 00:00:00 2001 From: Mahiv Ram Date: Mon, 16 Feb 2026 18:46:22 +0530 Subject: [PATCH 08/10] [IMP] estate: added some buttons and their methods and introduced exception handling Chapter 9: Ready For Some Action? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added the buttons ‘Cancel’ and ‘Sold’ to the estate.property model. A cancelled property cannot be set as sold, and a sold property cannot be cancelled by help of usererror. Added the buttons ‘Accept’ and ‘Refuse’ to the estate.property.offer model. When an offer is accepted, selling price automatically set as offer price for the corresponding property. also added the security rights and groups for estate property. --- estate/__manifest__.py | 1 + estate/models/estate_property.py | 18 +++++++++++++- estate/models/estate_property_offer.py | 13 ++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/security/res_groups.xml | 12 ++++++++++ estate/views/estate_property_offer_views.xml | 25 +++++--------------- estate/views/estate_property_views.xml | 4 ++++ 7 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 estate/security/res_groups.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index cc5cd450276..820963ffaa8 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,6 +4,7 @@ "description": "Specific Real Estate Module", "depends": ["base"], "data": [ + "security/res_groups.xml", "security/ir.model.access.csv", "views/estate_property_views.xml", "views/estate_property_tag_views.xml", diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a6b44c1e826..d89ac33f39d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,12 @@ from dateutil.relativedelta import relativedelta from odoo import fields, models, api +from odoo.exceptions import UserError class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Management Module" + name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Post Code") @@ -75,4 +77,18 @@ def onchange_garden(self): self.garden_orientation = "north" else: self.garden_area = 0 - self.garden_orientation = "" + self.garden_orientation = False + + def action_sold(self): + for record in self: + if record.state != 'canceled': + record.state = 'sold' + else: + raise UserError("You cannot move to canceled stage after sold the property") + + def action_cencel(self): + for record in self: + if record.state != 'sold': + record.state = 'canceled' + else: + raise UserError("You cannot move to sold stage after cenceled the property") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index d71433dab95..481ced1856d 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ from odoo import fields, models, api +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -53,3 +54,15 @@ def _onchange_validity(self): if self.date_deadline: create_date = fields.Date.to_date(self.create_date) or fields.Date.today() self.validity = (self.date_deadline - create_date).days + + def action_accept_offer(self): + for record in self: + if not (record.property_id.selling_price): + record.property_id.selling_price = record.price + record.status = "accepted" + else: + raise UserError("You cannot accept multiple offer") + + def action_refuse_offer(self): + for record in self: + record.status = "refuse" diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 7d3ba7eb3ac..b9786ca05f0 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -3,3 +3,5 @@ access_estate_property,estate.property,model_estate_property,base.group_user,1,1 access_estate_property_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_manager,estate.property manager,model_estate_property,group_estate_manager,1,1,1,1 +access_estate_property_user,estate.property user,model_estate_property,group_estate_user,1,0,1,0 diff --git a/estate/security/res_groups.xml b/estate/security/res_groups.xml new file mode 100644 index 00000000000..4d88e409f25 --- /dev/null +++ b/estate/security/res_groups.xml @@ -0,0 +1,12 @@ + + + + + Estate Manager + + + + Estate User + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index fd6b6479f40..5cc50f97753 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -5,31 +5,18 @@ estate.property.offer.view.list estate.property.offer - + +