From f6b793decd9bcd10a87116499a9de46f36effd78 Mon Sep 17 00:00:00 2001
From: pimai-odoo
Date: Mon, 27 Oct 2025 20:04:32 +0530
Subject: [PATCH 1/7] [ADD] estate: created a new module
Added a new 'real estate' module, created the database, and defined necessary
columns.
---
estate/__init__.py | 1 +
estate/__manifest__.py | 4 ++++
estate/models/__init__.py | 1 +
estate/models/estate_property.py | 23 +++++++++++++++++++++++
4 files changed, 29 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..a9545d463e3
--- /dev/null
+++ b/estate/__manifest__.py
@@ -0,0 +1,4 @@
+{
+ 'name': 'Real Estate',
+ 'depends': ['base'],
+}
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
new file mode 100644
index 00000000000..f4c8fd6db6d
--- /dev/null
+++ b/estate/models/__init__.py
@@ -0,0 +1 @@
+from . import estate_property
\ No newline at end of file
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
new file mode 100644
index 00000000000..6d907ee40d7
--- /dev/null
+++ b/estate/models/estate_property.py
@@ -0,0 +1,23 @@
+from odoo import models,fields
+
+class estateproperty(models.Model):
+ _name = "estate.property"
+ _description = "Real Estate Property"
+
+ name = fields.Char(required='True')
+ description = fields.Text(string="Description")
+ postcode = fields.Char(string="Postcode")
+ date_availability = fields.Date(string="Available From")
+ expected_price = fields.Float(string="Expected Price", required=True)
+ selling_price = fields.Float(string="Selling Price", readonly=True)
+ bedrooms = fields.Integer(string="Bedrooms", default=2)
+ living_area = fields.Integer(string="Living Area (sqm)")
+ facades = fields.Integer(string="Facades")
+ garage = fields.Boolean(string="Garage")
+ garden = fields.Boolean(string="Garden")
+ garden_area = fields.Integer(string="Garden Area (sqm)")
+ garden_orientation = fields.Selection(
+ selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West')],
+ string="Garden Orientation"
+ )
+ active = fields.Boolean(string="Active", default=True)
\ No newline at end of file
From c86ae0a4b9a27e87d137cad6b395d2efb3467e5d Mon Sep 17 00:00:00 2001
From: pimai-odoo
Date: Tue, 28 Oct 2025 19:09:09 +0530
Subject: [PATCH 2/7] [IMP] estate: update fields and menus in real estate
Set default values for fields, added new field attributes, and created menus
for accessing default list and form views in Real Estate module
---
estate/__init__.py | 2 +-
estate/__manifest__.py | 5 ++++
estate/models/__init__.py | 2 +-
estate/models/estate_property.py | 32 +++++++++++++++++++++-----
estate/security/ir.model.access.csv | 2 ++
estate/views/estate_menus.xml | 7 ++++++
estate/views/estate_property_views.xml | 12 ++++++++++
7 files changed, 54 insertions(+), 8 deletions(-)
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
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 a9545d463e3..0c63df47519 100644
--- a/estate/__manifest__.py
+++ b/estate/__manifest__.py
@@ -1,4 +1,9 @@
{
'name': 'Real Estate',
'depends': ['base'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/estate_property_views.xml',
+ 'views/estate_menus.xml'
+ ]
}
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
index f4c8fd6db6d..5e1963c9d2f 100644
--- a/estate/models/__init__.py
+++ b/estate/models/__init__.py
@@ -1 +1 @@
-from . import estate_property
\ No newline at end of file
+from . import estate_property
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 6d907ee40d7..16da4fbb6fd 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,13 +1,14 @@
-from odoo import models,fields
+from odoo import models, fields
+from dateutil.relativedelta import relativedelta
class estateproperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"
- name = fields.Char(required='True')
+ name = fields.Char(required=True)
description = fields.Text(string="Description")
postcode = fields.Char(string="Postcode")
- date_availability = fields.Date(string="Available From")
+ date_availability = fields.Date(string="Availability Date", default=fields.Date.today()+relativedelta(months=3))
expected_price = fields.Float(string="Expected Price", required=True)
selling_price = fields.Float(string="Selling Price", readonly=True)
bedrooms = fields.Integer(string="Bedrooms", default=2)
@@ -17,7 +18,26 @@ class estateproperty(models.Model):
garden = fields.Boolean(string="Garden")
garden_area = fields.Integer(string="Garden Area (sqm)")
garden_orientation = fields.Selection(
- selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West')],
- string="Garden Orientation"
+ selection=[
+ ("north", "North"),
+ ("south", "South"),
+ ("east", "East"),
+ ("west", "West"),
+ ],
+ string="Garden Orientation",
)
- active = fields.Boolean(string="Active", default=True)
\ No newline at end of file
+ state = fields.Selection(
+ [
+ ("new", "New"),
+ ("offer_received", "Offer Received"),
+ ("offer_accepted", "Offer Accepted"),
+ ("sold", "Sold"),
+ ("cancelled", "Cancelled"),
+ ],
+ string="Status",
+ required=True,
+ copy=False,
+ default="new",
+ )
+ active = fields.Boolean(string="Active", default=True)
+
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv
new file mode 100644
index 00000000000..98f4671fb0d
--- /dev/null
+++ b/estate/security/ir.model.access.csv
@@ -0,0 +1,2 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1
diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml
new file mode 100644
index 00000000000..96696b214c3
--- /dev/null
+++ b/estate/views/estate_menus.xml
@@ -0,0 +1,7 @@
+
+
+
\ 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..def2d0ea112
--- /dev/null
+++ b/estate/views/estate_property_views.xml
@@ -0,0 +1,12 @@
+
+
+ Properties
+ estate.property
+ list,form
+
+
+ Create a new property
+
+
+
+
From 3e951c1d9e8f1da3c0035828c282fc019cca4448 Mon Sep 17 00:00:00 2001
From: pimai-odoo
Date: Wed, 29 Oct 2025 15:52:44 +0530
Subject: [PATCH 3/7] [IMP] estate: add list/form/search views with group by in
Real Estate
Created list, form, and search views, and added group by functionality in the Real Estate.
---
estate/models/estate_property.py | 24 ++++-----
estate/views/estate_menus.xml | 2 +-
estate/views/estate_property_views.xml | 68 ++++++++++++++++++++++++++
3 files changed, 82 insertions(+), 12 deletions(-)
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 16da4fbb6fd..ea35038cf1a 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,22 +1,23 @@
from odoo import models, fields
from dateutil.relativedelta import relativedelta
+
class estateproperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"
name = fields.Char(required=True)
- description = fields.Text(string="Description")
- postcode = fields.Char(string="Postcode")
- date_availability = fields.Date(string="Availability Date", default=fields.Date.today()+relativedelta(months=3))
- expected_price = fields.Float(string="Expected Price", required=True)
- selling_price = fields.Float(string="Selling Price", readonly=True)
- bedrooms = fields.Integer(string="Bedrooms", default=2)
- living_area = fields.Integer(string="Living Area (sqm)")
- facades = fields.Integer(string="Facades")
- garage = fields.Boolean(string="Garage")
- garden = fields.Boolean(string="Garden")
- garden_area = fields.Integer(string="Garden Area (sqm)")
+ description = fields.Text("Description")
+ postcode = fields.Char("Postcode")
+ date_availability = fields.Date("Availability Date", default=fields.Date.today()+relativedelta(months=3))
+ expected_price = fields.Float("Expected Price", required=True)
+ selling_price = fields.Float("Selling Price", readonly=True)
+ bedrooms = fields.Integer("Bedrooms", default=2)
+ living_area = fields.Integer("Living Area (sqm)")
+ facades = fields.Integer("Facades")
+ garage = fields.Boolean("Garage")
+ garden = fields.Boolean("Garden")
+ garden_area = fields.Integer("Garden Area (sqm)")
garden_orientation = fields.Selection(
selection=[
("north", "North"),
@@ -40,4 +41,5 @@ class estateproperty(models.Model):
default="new",
)
active = fields.Boolean(string="Active", default=True)
+
diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml
index 96696b214c3..47be4a68c82 100644
--- a/estate/views/estate_menus.xml
+++ b/estate/views/estate_menus.xml
@@ -4,4 +4,4 @@
-
\ No newline at end of file
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
index def2d0ea112..9c08278e011 100644
--- a/estate/views/estate_property_views.xml
+++ b/estate/views/estate_property_views.xml
@@ -9,4 +9,72 @@
+
+
+ estate.property.view.list
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate.property.view.form
+ estate.property
+
+
+
+
+
+
+ estate.property.view.search
+ estate.property
+
+
+
+
+
+
+
+
+
+
From 17de6432be4d98f70b2cbeeff7a9f454193a61b8 Mon Sep 17 00:00:00 2001
From: pimai-odoo
Date: Thu, 30 Oct 2025 17:11:14 +0530
Subject: [PATCH 4/7] [IMP] estate: added property type,tags,buyer,salesperson
& offers in Real Estate
Added Property Type and Tag models, buyer and salesperson fields,
and the Property Offer model in the Real Estate module.
---
estate/__manifest__.py | 10 +++++--
estate/models/__init__.py | 3 ++
estate/models/estate_property.py | 30 +++++++++++---------
estate/models/estate_property_offer.py | 15 ++++++++++
estate/models/estate_property_tag.py | 8 ++++++
estate/models/estate_property_type.py | 8 ++++++
estate/security/ir.model.access.csv | 3 ++
estate/views/estate_menus.xml | 29 +++++++++++++++----
estate/views/estate_property_offer_views.xml | 29 +++++++++++++++++++
estate/views/estate_property_tag_views.xml | 7 +++++
estate/views/estate_property_type_views.xml | 7 +++++
estate/views/estate_property_views.xml | 29 +++++++++++++++++++
12 files changed, 158 insertions(+), 20 deletions(-)
create mode 100644 estate/models/estate_property_offer.py
create mode 100644 estate/models/estate_property_tag.py
create mode 100644 estate/models/estate_property_type.py
create mode 100644 estate/views/estate_property_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 0c63df47519..41b6cf0a752 100644
--- a/estate/__manifest__.py
+++ b/estate/__manifest__.py
@@ -4,6 +4,12 @@
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
- 'views/estate_menus.xml'
- ]
+ 'views/estate_menus.xml',
+ 'views/estate_property_type_views.xml',
+ 'views/estate_property_tag_views.xml',
+ 'views/estate_property_offer_views.xml'
+ ],
+ 'application': True,
+ 'installable':True
}
+
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
index 5e1963c9d2f..2f1821a39c1 100644
--- a/estate/models/__init__.py
+++ b/estate/models/__init__.py
@@ -1 +1,4 @@
from . import estate_property
+from . import estate_property_type
+from . import estate_property_tag
+from . import estate_property_offer
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index ea35038cf1a..412fa33fb9e 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -2,22 +2,22 @@
from dateutil.relativedelta import relativedelta
-class estateproperty(models.Model):
+class EstateProperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"
name = fields.Char(required=True)
- description = fields.Text("Description")
- postcode = fields.Char("Postcode")
- date_availability = fields.Date("Availability Date", default=fields.Date.today()+relativedelta(months=3))
+ description = fields.Text()
+ postcode = fields.Char()
+ date_availability = fields.Date("Availability Date", default=fields.Date.today() + relativedelta(months=3))
expected_price = fields.Float("Expected Price", required=True)
selling_price = fields.Float("Selling Price", readonly=True)
- bedrooms = fields.Integer("Bedrooms", default=2)
- living_area = fields.Integer("Living Area (sqm)")
- facades = fields.Integer("Facades")
- garage = fields.Boolean("Garage")
- garden = fields.Boolean("Garden")
- garden_area = fields.Integer("Garden Area (sqm)")
+ bedrooms = fields.Integer(default=2)
+ living_area = fields.Integer("Living Area(sqm)")
+ facades = fields.Integer()
+ garage = fields.Boolean()
+ garden = fields.Boolean()
+ garden_area = fields.Integer("Garden Area(sqm)")
garden_orientation = fields.Selection(
selection=[
("north", "North"),
@@ -40,6 +40,10 @@ class estateproperty(models.Model):
copy=False,
default="new",
)
- active = fields.Boolean(string="Active", default=True)
-
-
+ active = fields.Boolean(default=True)
+ property_type_id = fields.Many2one("estate.property.type", string="Property Type")
+ buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
+ salesperson_id = fields.Many2one(
+ "res.users", string="Salesperson")
+ tag_ids = fields.Many2many("estate.property.tag", string="Tags")
+ 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..788fc12b6c8
--- /dev/null
+++ b/estate/models/estate_property_offer.py
@@ -0,0 +1,15 @@
+from odoo import fields, models
+
+
+class EstatePropertyOffer(models.Model):
+ _name = "estate.property.offer"
+ _description = "Real Estate Property Offer"
+
+ 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)
+
\ No newline at end of file
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
new file mode 100644
index 00000000000..198c1037c41
--- /dev/null
+++ b/estate/models/estate_property_tag.py
@@ -0,0 +1,8 @@
+from odoo import fields, models
+
+
+class EstatePropertyTag(models.Model):
+ _name = "estate.property.tag"
+ _description = "Real Estate Property Tag"
+
+ name = fields.Char(required=True)
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
new file mode 100644
index 00000000000..ced40ef01cc
--- /dev/null
+++ b/estate/models/estate_property_type.py
@@ -0,0 +1,8 @@
+from odoo import fields, models
+
+
+class EstatePropertyType(models.Model):
+ _name = "estate.property.type"
+ _description = "Real Estate Property Type"
+
+ name = fields.Char(required=True)
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv
index 98f4671fb0d..58f5c8d8fe6 100644
--- a/estate/security/ir.model.access.csv
+++ b/estate/security/ir.model.access.csv
@@ -1,2 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1
+estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1
+access_estate_property_tag,estate.property.tag.access,model_estate_property_tag,base.group_user,1,1,1,1
+access_estate_property_offer,estate.property.offer.access,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 47be4a68c82..61b729e178c 100644
--- a/estate/views/estate_menus.xml
+++ b/estate/views/estate_menus.xml
@@ -1,7 +1,26 @@
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml
new file mode 100644
index 00000000000..1fbb3be50a0
--- /dev/null
+++ b/estate/views/estate_property_offer_views.xml
@@ -0,0 +1,29 @@
+
+
+ estate.property.offer.list
+ estate.property.offer
+
+
+
+
+
+
+
+
+
+
+ estate.property.offer.form
+ estate.property.offer
+
+
+
+
+
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml
new file mode 100644
index 00000000000..70ccfa7ea83
--- /dev/null
+++ b/estate/views/estate_property_tag_views.xml
@@ -0,0 +1,7 @@
+
+
+ Property Tags
+ estate.property.tag
+ list,form
+
+
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml
new file mode 100644
index 00000000000..382925bd424
--- /dev/null
+++ b/estate/views/estate_property_type_views.xml
@@ -0,0 +1,7 @@
+
+
+ Property Types
+ estate.property.type
+ list,form
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
index 9c08278e011..f83c441c093 100644
--- a/estate/views/estate_property_views.xml
+++ b/estate/views/estate_property_views.xml
@@ -22,6 +22,8 @@
+
+
@@ -37,6 +39,8 @@
+
+
@@ -54,6 +58,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -68,6 +96,7 @@
+
Date: Fri, 31 Oct 2025 18:12:23 +0530
Subject: [PATCH 5/7] [IMP] estate: add computed fields,inverse funcs, onchange
& Sold/Cancel buttons
Added computed fields with inverse functions and onchange methods,
along with Sold and Cancel buttons to enhance the
Real Estate module functionality.
---
estate/__manifest__.py | 1 -
estate/models/estate_property.py | 48 ++++++++++++++++++--
estate/models/estate_property_offer.py | 21 ++++++++-
estate/models/estate_property_tag.py | 1 +
estate/models/estate_property_type.py | 1 +
estate/views/estate_property_offer_views.xml | 10 ++--
estate/views/estate_property_views.xml | 11 +++++
7 files changed, 83 insertions(+), 10 deletions(-)
diff --git a/estate/__manifest__.py b/estate/__manifest__.py
index 41b6cf0a752..8d29e3c8469 100644
--- a/estate/__manifest__.py
+++ b/estate/__manifest__.py
@@ -12,4 +12,3 @@
'application': True,
'installable':True
}
-
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 412fa33fb9e..91314582202 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,5 +1,7 @@
-from odoo import models, fields
+from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
+from datetime import date
+from odoo.exceptions import UserError
class EstateProperty(models.Model):
@@ -35,15 +37,53 @@ class EstateProperty(models.Model):
("sold", "Sold"),
("cancelled", "Cancelled"),
],
- string="Status",
+ "Status",
required=True,
copy=False,
default="new",
)
active = fields.Boolean(default=True)
- property_type_id = fields.Many2one("estate.property.type", string="Property Type")
+ property_type_id = fields.Many2one("estate.property.type", "Property Type")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
salesperson_id = fields.Many2one(
"res.users", string="Salesperson")
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
- offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
+ offer_ids = fields.One2many("estate.property.offer", "property_id", "Offers")
+ total_area = fields.Integer("Total Area(sqm)", compute="_compute_total_area")
+ best_price = fields.Float("Best Offer", compute="_compute_best_price")
+
+ @api.depends("living_area", "garden_area")
+ def _compute_total_area(self):
+ for record in self:
+ record.total_area = record.living_area + record.garden_area
+
+ @api.depends("offer_ids.price")
+ def _compute_best_price(self):
+ for record in self:
+ if record.offer_ids:
+ record.best_price = max(record.offer_ids.mapped("price"))
+ else:
+ record.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
+
+ def action_cancel(self):
+ for record in self:
+ if record.state == 'sold':
+ raise UserError("A sold property cannot be cancelled")
+ else:
+ self.state = 'cancelled'
+
+ def action_sold(self):
+ for record in self:
+ if record.state == 'cancelled':
+ raise UserError("A cancelled property cannot be set as sold")
+ else:
+ self.state = 'sold'
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
index 788fc12b6c8..888b2ea13aa 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 odoo import fields, models, api
+from dateutil.relativedelta import relativedelta
class EstatePropertyOffer(models.Model):
@@ -12,4 +13,20 @@ class EstatePropertyOffer(models.Model):
)
partner_id = fields.Many2one("res.partner", string="Partner", required=True)
property_id = fields.Many2one("estate.property", string="Property", required=True)
-
\ No newline at end of file
+ validity = fields.Integer(string="Validity(days)", default=7)
+ date_deadline = fields.Date(string="Deadline Date", compute="_compute_date_deadline", inverse="_inverse_date_deadline")
+
+ @api.depends('validity')
+ def _compute_date_deadline(self):
+ for record in self:
+ if record.create_date:
+ record.date_deadline = fields.Date.to_date(record.create_date) + relativedelta(days=record.validity)
+ else:
+ record.date_deadline = False
+
+ def _inverse_date_deadline(self):
+ for record in self:
+ if record.create_date:
+ record.validity = (record.date_deadline - fields.Date.to_date(record.create_date)).days
+ else:
+ record.validity = False
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
index 198c1037c41..380acf06f24 100644
--- a/estate/models/estate_property_tag.py
+++ b/estate/models/estate_property_tag.py
@@ -6,3 +6,4 @@ class EstatePropertyTag(models.Model):
_description = "Real Estate Property Tag"
name = fields.Char(required=True)
+
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
index ced40ef01cc..6e26abafb14 100644
--- a/estate/models/estate_property_type.py
+++ b/estate/models/estate_property_type.py
@@ -6,3 +6,4 @@ class EstatePropertyType(models.Model):
_description = "Real Estate Property Type"
name = fields.Char(required=True)
+
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml
index 1fbb3be50a0..c67efab427c 100644
--- a/estate/views/estate_property_offer_views.xml
+++ b/estate/views/estate_property_offer_views.xml
@@ -7,6 +7,8 @@
+
+
@@ -18,9 +20,11 @@
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
index f83c441c093..ad424b6a79d 100644
--- a/estate/views/estate_property_views.xml
+++ b/estate/views/estate_property_views.xml
@@ -33,6 +33,10 @@
estate.property
From 6f8f97b8f8f616d3e43690ff43aec5d2031d0ef6 Mon Sep 17 00:00:00 2001
From: pimai-odoo
Date: Mon, 3 Nov 2025 15:53:52 +0530
Subject: [PATCH 6/7] [IMP] estate: added Accept,Refuse buttons & related
functionality in Real Estate
Added Accept and Refuse buttons along with the supporting functionality
to improve workflow in the Real Estate module.
---
estate/__manifest__.py | 8 +++++---
estate/models/estate_property.py | 7 +++----
estate/models/estate_property_offer.py | 13 +++++++++++++
estate/models/estate_property_tag.py | 1 -
estate/models/estate_property_type.py | 1 -
estate/views/estate_property_offer_views.xml | 2 ++
estate/views/estate_property_views.xml | 16 ----------------
7 files changed, 23 insertions(+), 25 deletions(-)
diff --git a/estate/__manifest__.py b/estate/__manifest__.py
index 8d29e3c8469..06df611a27f 100644
--- a/estate/__manifest__.py
+++ b/estate/__manifest__.py
@@ -4,11 +4,13 @@
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
- 'views/estate_menus.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
- 'views/estate_property_offer_views.xml'
+ 'views/estate_property_offer_views.xml',
+ 'views/estate_menus.xml'
],
'application': True,
- 'installable':True
+ 'installable': True,
+ 'author': 'Odoo S.A.',
+ 'license': 'LGPL-3'
}
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 91314582202..87d900e72de 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,6 +1,5 @@
from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
-from datetime import date
from odoo.exceptions import UserError
@@ -51,12 +50,12 @@ class EstateProperty(models.Model):
offer_ids = fields.One2many("estate.property.offer", "property_id", "Offers")
total_area = fields.Integer("Total Area(sqm)", compute="_compute_total_area")
best_price = fields.Float("Best Offer", compute="_compute_best_price")
-
+
@api.depends("living_area", "garden_area")
def _compute_total_area(self):
for record in self:
record.total_area = record.living_area + record.garden_area
-
+
@api.depends("offer_ids.price")
def _compute_best_price(self):
for record in self:
@@ -64,7 +63,7 @@ def _compute_best_price(self):
record.best_price = max(record.offer_ids.mapped("price"))
else:
record.best_price = 0
-
+
@api.onchange('garden')
def _onchange_garden(self):
if self.garden:
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
index 888b2ea13aa..1af415736b1 100644
--- a/estate/models/estate_property_offer.py
+++ b/estate/models/estate_property_offer.py
@@ -30,3 +30,16 @@ def _inverse_date_deadline(self):
record.validity = (record.date_deadline - fields.Date.to_date(record.create_date)).days
else:
record.validity = False
+
+ def action_accept(self):
+ for record in self:
+ record.status = "accepted"
+ record.property_id.selling_price = record.price
+ record.property_id.buyer_id = record.partner_ids
+ record.property_id.state = "offer_accepted"
+ return True
+
+ def action_refuse(self):
+ for record in self:
+ record.status = 'refused'
+ return True
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
index 380acf06f24..198c1037c41 100644
--- a/estate/models/estate_property_tag.py
+++ b/estate/models/estate_property_tag.py
@@ -6,4 +6,3 @@ class EstatePropertyTag(models.Model):
_description = "Real Estate Property Tag"
name = fields.Char(required=True)
-
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
index 6e26abafb14..ced40ef01cc 100644
--- a/estate/models/estate_property_type.py
+++ b/estate/models/estate_property_type.py
@@ -6,4 +6,3 @@ class EstatePropertyType(models.Model):
_description = "Real Estate Property Type"
name = fields.Char(required=True)
-
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml
index c67efab427c..1a6420f98bd 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 ad424b6a79d..58c72db14fa 100644
--- a/estate/views/estate_property_views.xml
+++ b/estate/views/estate_property_views.xml
@@ -68,22 +68,6 @@
-
-
-
-
-
-
-
-
From 50a55460bba932feb9bed8807d41389c05f31ccb Mon Sep 17 00:00:00 2001
From: pimai-odoo
Date: Tue, 4 Nov 2025 16:50:17 +0530
Subject: [PATCH 7/7] [IMP] estate: add SQL & Python constraints for price &
uniqueness in Real Estate
Added SQL constraints to ensure positive expected, selling,
and offer prices, and unique names for property tags and types.
Added a Python constraint to ensure the selling price
is not less than 90% of the expected price.
---
estate/models/estate_property.py | 26 +++++++++++++++++---
estate/models/estate_property_offer.py | 33 +++++++++++++++-----------
estate/models/estate_property_tag.py | 5 ++++
estate/models/estate_property_type.py | 5 ++++
4 files changed, 52 insertions(+), 17 deletions(-)
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
index 87d900e72de..f59feeec6a0 100644
--- a/estate/models/estate_property.py
+++ b/estate/models/estate_property.py
@@ -1,6 +1,9 @@
-from odoo import models, fields, api
from dateutil.relativedelta import relativedelta
+
+from odoo import models, fields, api
from odoo.exceptions import UserError
+from odoo.exceptions import ValidationError
+from odoo.tools.float_utils import float_compare, float_is_zero
class EstateProperty(models.Model):
@@ -43,10 +46,10 @@ class EstateProperty(models.Model):
)
active = fields.Boolean(default=True)
property_type_id = fields.Many2one("estate.property.type", "Property Type")
- buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
+ buyer_id = fields.Many2one("res.partner", "Buyer", copy=False)
salesperson_id = fields.Many2one(
"res.users", string="Salesperson")
- tag_ids = fields.Many2many("estate.property.tag", string="Tags")
+ tag_ids = fields.Many2many("estate.property.tag", "Tags")
offer_ids = fields.One2many("estate.property.offer", "property_id", "Offers")
total_area = fields.Integer("Total Area(sqm)", compute="_compute_total_area")
best_price = fields.Float("Best Offer", compute="_compute_best_price")
@@ -86,3 +89,20 @@ def action_sold(self):
raise UserError("A cancelled property cannot be set as sold")
else:
self.state = 'sold'
+
+ _check_expected_price = models.Constraint(
+ 'CHECK(expected_price > 0)',
+ 'The expected price of a property must be strictly positive.'
+ )
+
+ _check_selling_price = models.Constraint(
+ 'CHECK(selling_price > 0)',
+ 'The selling price of a property must be positive.'
+ )
+
+ @api.constrains('selling_price', 'expected_price')
+ def _check_selling_price(self):
+ for record in self:
+ if not float_is_zero(record.selling_price, precision_digits=2):
+ if float_compare(record.selling_price, record.expected_price * 0.9, precision_digits=2) < 0:
+ raise ValidationError("The selling price cannot be lower than 90% of the expected price.")
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
index 1af415736b1..a495e6135e2 100644
--- a/estate/models/estate_property_offer.py
+++ b/estate/models/estate_property_offer.py
@@ -1,6 +1,8 @@
-from odoo import fields, models, api
from dateutil.relativedelta import relativedelta
+from odoo import fields, models, api
+from odoo.exceptions import UserError
+
class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
@@ -19,27 +21,30 @@ class EstatePropertyOffer(models.Model):
@api.depends('validity')
def _compute_date_deadline(self):
for record in self:
- if record.create_date:
- record.date_deadline = fields.Date.to_date(record.create_date) + relativedelta(days=record.validity)
- else:
- record.date_deadline = False
+ creation_date = record.create_date or fields.Date.today()
+ record.date_deadline = relativedelta(days=record.validity) + creation_date
def _inverse_date_deadline(self):
for record in self:
- if record.create_date:
- record.validity = (record.date_deadline - fields.Date.to_date(record.create_date)).days
- else:
- record.validity = False
+ creation_date = record.create_date or fields.Date.today()
+ record.validity = (record.date_deadline - fields.Date.to_date(creation_date)).days
def action_accept(self):
for record in self:
- record.status = "accepted"
- record.property_id.selling_price = record.price
- record.property_id.buyer_id = record.partner_ids
- record.property_id.state = "offer_accepted"
- return True
+ if record.property_id.buyer_id:
+ raise UserError("Property already accepted")
+ else:
+ record.status = 'accepted'
+ record.property_id.selling_price = record.price
+ record.property_id.state = 'offer_accepted'
+ record.property_id.buyer_id= record.partner_id
def action_refuse(self):
for record in self:
record.status = 'refused'
return True
+
+ _check_offer_price = models.Constraint(
+ 'CHECK(price > 0)',
+ 'The price of an offer must be strictly positive.'
+ )
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
index 198c1037c41..04b06249ebe 100644
--- a/estate/models/estate_property_tag.py
+++ b/estate/models/estate_property_tag.py
@@ -6,3 +6,8 @@ class EstatePropertyTag(models.Model):
_description = "Real Estate Property Tag"
name = fields.Char(required=True)
+
+ _check_tag_name_unique = models.Constraint(
+ 'UNIQUE(name)',
+ 'The name of the property tag must be unique.'
+ )
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
index ced40ef01cc..9dbc82c2afe 100644
--- a/estate/models/estate_property_type.py
+++ b/estate/models/estate_property_type.py
@@ -6,3 +6,8 @@ class EstatePropertyType(models.Model):
_description = "Real Estate Property Type"
name = fields.Char(required=True)
+
+ _check_type_name_unique = models.Constraint(
+ 'UNIQUE(name)',
+ 'The name of the property type must be unique.'
+ )