From 94ed96cf4f914ec4b07153ab1632e473be423860 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Wed, 22 Apr 2026 10:07:10 +0200 Subject: [PATCH 01/17] [ADD] estate: basic module creation Created init and manifest files --- estate/__init__.py | 0 estate/__manifest__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..e69de29bb2d From 3702745e87ab5f1d75441c60479d473fb38ebe37 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Wed, 22 Apr 2026 14:13:52 +0200 Subject: [PATCH 02/17] [ADD] estate: models and basic fields Added fields in __manifest__.py Created models folder, EstateProperty model with the required fields and imported it in __manifest__.py files --- estate/__init__.py | 1 + estate/__manifest__.py | 19 +++++++++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) 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 index e69de29bb2d..9a7e03eded3 100644 --- a/estate/__init__.py +++ 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 index e69de29bb2d..c18fcc5cb48 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -0,0 +1,19 @@ +{ + 'name': "Estate", + 'summary': """ + The Real Estate Advertisement module + """, + + 'description': """ + The Real Estate Advertisement module + """, + 'author': "Odoo", + 'website': "https://www.odoo.com/", + 'category': 'Tutorials', + 'version': '0.1', + 'application': True, + 'installable': True, + 'depends': ['base'], + 'data': [], + 'license': 'AGPL-3' +} \ No newline at end of file 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..40a70083989 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,28 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + + name = fields.Char('Property Name', required=True) + description = fields.Text('Description') + postcode = fields.Char('Postcode') + date_availability = fields.Date('Availability Date') + expected_price = fields.Float('Expected Price', required=True) + selling_price = fields.Float('Selling Price') + bedrooms = fields.Integer('Bedrooms') + living_area = fields.Integer('Living Area') + facades = fields.Integer('Facades') + garage = fields.Boolean('Garage') + garden = fields.Boolean('Garden') + garden_area = fields.Integer('Garden Area') + garden_orientation = fields.Selection( + string='Garden Orientation', + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ], + ) From ef29dbeb8acfa0f14df397023fa5674fc5a2ed80 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Wed, 22 Apr 2026 15:27:49 +0200 Subject: [PATCH 03/17] [ADD] estate: add access rights for property model Created data folder and file ir.model.access.csv inside it giving read, write, create and unlink permissions to the group base.group_user --- estate/__manifest__.py | 4 +++- estate/data/ir.model.access.csv | 2 ++ estate/models/estate_property.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 estate/data/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index c18fcc5cb48..4c24413802b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,6 +14,8 @@ 'application': True, 'installable': True, 'depends': ['base'], - 'data': [], + 'data': [ + 'data/ir.model.access.csv', + ], 'license': 'AGPL-3' } \ No newline at end of file diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /dev/null +++ b/estate/data/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 \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 40a70083989..b9b1db50fe0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -25,4 +25,4 @@ class EstateProperty(models.Model): ('east', 'East'), ('west', 'West'), ], - ) + ) \ No newline at end of file From 2ba81c04e36443c818b5cac8e254d9ff39663593 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Wed, 22 Apr 2026 17:21:58 +0200 Subject: [PATCH 04/17] [ADD] estate: add basic user interface Added views and actions for estate.property. Added a 3 level menu with the basic action Added and improved attributes --- estate/__manifest__.py | 2 ++ estate/data/estate_menus.xml | 7 +++++++ estate/models/estate_property.py | 20 ++++++++++++++++---- estate/views/estate_property_views.xml | 7 +++++++ 4 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 estate/data/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4c24413802b..d0bb1fc9215 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -16,6 +16,8 @@ 'depends': ['base'], 'data': [ 'data/ir.model.access.csv', + 'views/estate_property_views.xml', + 'data/estate_menus.xml', ], 'license': 'AGPL-3' } \ No newline at end of file diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml new file mode 100644 index 00000000000..7cd5a90cf72 --- /dev/null +++ b/estate/data/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b9b1db50fe0..000ec757b3e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -4,14 +4,14 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property" - + active = True name = fields.Char('Property Name', required=True) description = fields.Text('Description') postcode = fields.Char('Postcode') - date_availability = fields.Date('Availability Date') + date_availability = fields.Date('Availability Date', copy=False, default=fields.Date.add(fields.Date.today(), months=3)) expected_price = fields.Float('Expected Price', required=True) - selling_price = fields.Float('Selling Price') - bedrooms = fields.Integer('Bedrooms') + selling_price = fields.Float('Selling Price', readonly=True, copy=False) + bedrooms = fields.Integer('Bedrooms', default=2) living_area = fields.Integer('Living Area') facades = fields.Integer('Facades') garage = fields.Boolean('Garage') @@ -25,4 +25,16 @@ class EstateProperty(models.Model): ('east', 'East'), ('west', 'West'), ], + ) + state = fields.Selection( + string='State', + selection=[ + ('new', 'New'), + ('offer received', 'Offer Received'), + ('offer accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + default='new', + required=True, ) \ 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..82ea3362c85 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate.property + list,form + + From 6aa8bbed6102f1f256debd40ca198451f98a0c19 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Thu, 23 Apr 2026 09:56:20 +0200 Subject: [PATCH 05/17] [CLN] estate: implemented requested changes - Added newlines to the relevant files - Changed license and version - Moved menus form data to views folder - Now follows formatting rules --- estate/__init__.py | 2 +- estate/__manifest__.py | 13 +++++++------ estate/data/estate_menus.xml | 7 ------- estate/data/ir.model.access.csv | 2 +- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 3 ++- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 4 ++-- 8 files changed, 21 insertions(+), 19 deletions(-) delete mode 100644 estate/data/estate_menus.xml create mode 100644 estate/views/estate_menus.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 d0bb1fc9215..b34f99b9d8b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,14 +10,15 @@ 'author': "Odoo", 'website': "https://www.odoo.com/", 'category': 'Tutorials', - 'version': '0.1', + 'version': '19.0.0.1.0', 'application': True, - 'installable': True, - 'depends': ['base'], + 'depends': [ + 'base', + ], 'data': [ 'data/ir.model.access.csv', 'views/estate_property_views.xml', - 'data/estate_menus.xml', + 'views/estate_menus.xml', ], - 'license': 'AGPL-3' -} \ No newline at end of file + 'license': 'LGPL-3', +} diff --git a/estate/data/estate_menus.xml b/estate/data/estate_menus.xml deleted file mode 100644 index 7cd5a90cf72..00000000000 --- a/estate/data/estate_menus.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv index 0e11f47e58d..e358a621c01 100644 --- a/estate/data/ir.model.access.csv +++ b/estate/data/ir.model.access.csv @@ -1,2 +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 \ No newline at end of file +estate_property_access_user,estate.property.user,model_estate_property,base.group_user,1,1,1,1 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 000ec757b3e..da9ece37b77 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -37,4 +37,5 @@ class EstateProperty(models.Model): ], default='new', required=True, - ) \ No newline at end of file + ) + \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..84fbbc65d08 --- /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 index 82ea3362c85..a81214e39f4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,7 +1,7 @@ - + Properties estate.property list,form - + \ No newline at end of file From 70fa394d8618a4288945a1fd4df98be9d046e9dc Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Thu, 23 Apr 2026 13:21:47 +0200 Subject: [PATCH 06/17] [ADD] estate: add views Added list views to main menu Added form view when creating property Added search views, filters and group --- estate/models/estate_property.py | 1 - estate/views/estate_menus.xml | 2 +- estate/views/estate_property_views.xml | 80 +++++++++++++++++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index da9ece37b77..9fae4165e18 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -38,4 +38,3 @@ class EstateProperty(models.Model): default='new', required=True, ) - \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 84fbbc65d08..6f7febd8738 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 a81214e39f4..5a8b054d5d3 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,7 +1,85 @@ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + estate.property.list + estate.property + + + + + + + + + + + + + Properties estate.property list,form -
\ No newline at end of file + From 32f65b1f8834454ecffcb779588c14534d76a2cb Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Fri, 24 Apr 2026 09:49:02 +0200 Subject: [PATCH 07/17] [ADD] estate: add models and relations Added property types and its menu and views Added property tags and its menu and views Added property offers and its views Added necessary fields and links --- estate/__manifest__.py | 3 ++ estate/data/ir.model.access.csv | 3 ++ estate/models/__init__.py | 2 +- estate/models/estate_property.py | 10 ++++--- estate/models/estate_property_offer.py | 16 +++++++++++ estate/models/estate_property_tag.py | 7 +++++ estate/models/estate_property_type.py | 7 +++++ estate/views/estate_menus.xml | 5 ++++ estate/views/estate_property_offer_views.xml | 29 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 21 ++++++++++++++ estate/views/estate_property_type_views.xml | 19 +++++++++++++ estate/views/estate_property_views.xml | 13 +++++++++ 12 files changed, 130 insertions(+), 5 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 b34f99b9d8b..f302c9b1e9a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -18,6 +18,9 @@ 'data': [ 'data/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', ], 'license': 'LGPL-3', diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv index e358a621c01..b2f2cb31b05 100644 --- a/estate/data/ir.model.access.csv +++ b/estate/data/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_property_access_user,estate.property.user,model_estate_property,base.group_user,1,1,1,1 +estate_property_type_access_user,estate.property.type.user,model_estate_property_type,base.group_user,1,1,1,1 +estate_property_tag_access_user,estate.property.tag.user,model_estate_property_tag,base.group_user,1,1,1,1 +estate_property_offer_access_user,estate.property.offer.user,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..93a6bd86abd 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property +from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9fae4165e18..2ababe59a45 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -4,11 +4,10 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property" - active = True name = fields.Char('Property Name', required=True) description = fields.Text('Description') postcode = fields.Char('Postcode') - date_availability = fields.Date('Availability Date', copy=False, default=fields.Date.add(fields.Date.today(), months=3)) + date_availability = fields.Date('Availability Date', copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) expected_price = fields.Float('Expected Price', required=True) selling_price = fields.Float('Selling Price', readonly=True, copy=False) bedrooms = fields.Integer('Bedrooms', default=2) @@ -18,7 +17,6 @@ class EstateProperty(models.Model): garden = fields.Boolean('Garden') garden_area = fields.Integer('Garden Area') garden_orientation = fields.Selection( - string='Garden Orientation', selection=[ ('north', 'North'), ('south', 'South'), @@ -27,7 +25,6 @@ class EstateProperty(models.Model): ], ) state = fields.Selection( - string='State', selection=[ ('new', 'New'), ('offer received', 'Offer Received'), @@ -38,3 +35,8 @@ class EstateProperty(models.Model): default='new', required=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", default=lambda self: self.env.uid) + tag_ids = fields.Many2many("estate.property.tag", string="Property Tag") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offer") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..885d9b5a0eb --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,16 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + price = fields.Float("Price") + status = fields.Selection( + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + copy=False, + ) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..afa8539fd0a --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tag" + name = fields.Char('Property Tag Name', required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..6063ea8de2f --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + name = fields.Char('Property Type Name', required=True) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 6f7febd8738..a55cc952013 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -3,5 +3,10 @@ + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..26883470dc1 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,29 @@ + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+ + + estate.property.offer.list + 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..f902320406f --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,21 @@ + + + estate.property.tag.form + estate.property.tag + +
+ + + + + +
+
+
+ + + 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..c129bddabee --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,19 @@ + + + estate.property.type.form + estate.property.type + +
+

+ +

+
+
+
+ + + Property Types + estate.property.type + list,form + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5a8b054d5d3..63aabda0f73 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -32,8 +32,10 @@ + + @@ -55,6 +57,17 @@ + + + + + + + + + + + From 00ddb13e5a920f9ef7394b5d61e27c1ef72d0eec Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Fri, 24 Apr 2026 11:30:56 +0200 Subject: [PATCH 08/17] [ADD] estate: computed fields and onchanges Added total area (garden area + living area) Added best price Added validity date Added onchanges on garden fields --- estate/models/estate_property.py | 34 ++++++++++++++++---- estate/models/estate_property_offer.py | 27 +++++++++++++++- estate/views/estate_property_offer_views.xml | 8 +++-- estate/views/estate_property_views.xml | 2 ++ 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2ababe59a45..d4e81edc9f1 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -15,6 +15,16 @@ class EstateProperty(models.Model): facades = fields.Integer('Facades') garage = fields.Boolean('Garage') garden = fields.Boolean('Garden') + + @api.onchange('garden') + def _onchange_garden(self): + if not self.garden: + self.garden_area = 0 + self.garden_orientation = False + else: + self.garden_area = 10 + self.garden_orientation = 'north' + garden_area = fields.Integer('Garden Area') garden_orientation = fields.Selection( selection=[ @@ -35,8 +45,20 @@ class EstateProperty(models.Model): default='new', required=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", default=lambda self: self.env.uid) - tag_ids = fields.Many2many("estate.property.tag", string="Property Tag") - offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offer") + 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", default=lambda self: self.env.uid) + tag_ids = fields.Many2many('estate.property.tag', string="Property Tag") + offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offer") + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for line in self: + line.total_area = line.living_area + line.garden_area + total_area = fields.Integer('Total Area', compute="_compute_total_area") + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for line in self: + line.best_price = max(line.mapped('offer_ids.price') or [0]) + best_price = fields.Float('Best Offer', compute="_compute_best_price") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 885d9b5a0eb..e081984a1de 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 api, fields, models +from datetime import timedelta class EstatePropertyOffer(models.Model): @@ -14,3 +15,27 @@ 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("Validity (days)", default=7) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for offer in self: + if offer.validity: + if offer.create_date: + offer.date_deadline = offer.create_date + timedelta(days=offer.validity) + else: + offer.date_deadline = fields.Date.today() + timedelta(days=offer.validity) + else: + offer.date_deadline = False + + def _inverse_date_deadline(self): + for offer in self: + if offer.date_deadline: + if offer.create_date: + offer.validity = (offer.date_deadline - offer.create_date).days + else: + offer.validity = (offer.date_deadline - fields.Date.today()).days + else: + offer.validity = 0 + + date_deadline = fields.Date("Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 26883470dc1..2097a5cccec 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + @@ -20,8 +22,10 @@ estate.property.offer - - + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 63aabda0f73..70fe806fbde 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -41,6 +41,7 @@ + @@ -55,6 +56,7 @@ + From 3cb24f93e533d65cbca0836546efe9a6c1598f40 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Mon, 27 Apr 2026 09:56:32 +0200 Subject: [PATCH 09/17] [ADD] estate: added actions Added action to cancel or set a property as sold Added action to accept or refuse offer and set selling price and buyer --- estate/models/estate_property.py | 31 ++++++++++++++++++-- estate/models/estate_property_offer.py | 20 +++++++++++-- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 6 ++++ 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index d4e81edc9f1..91f0a4f977e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import api, fields, models, exceptions class EstateProperty(models.Model): @@ -9,7 +9,7 @@ class EstateProperty(models.Model): postcode = fields.Char('Postcode') date_availability = fields.Date('Availability Date', copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) expected_price = fields.Float('Expected Price', required=True) - selling_price = fields.Float('Selling Price', readonly=True, copy=False) + #selling_price = fields.Float('Selling Price', readonly=True, copy=False) bedrooms = fields.Integer('Bedrooms', default=2) living_area = fields.Integer('Living Area') facades = fields.Integer('Facades') @@ -62,3 +62,30 @@ def _compute_best_price(self): for line in self: line.best_price = max(line.mapped('offer_ids.price') or [0]) best_price = fields.Float('Best Offer', compute="_compute_best_price") + + @api.depends('offer_ids.status', 'offer_ids.price') + def _compute_selling_price(self): + for line in self: + accepted_offer = line.offer_ids.filtered(lambda o: o.status == 'accepted') + if accepted_offer: + line.selling_price = accepted_offer[0].price + line.buyer_id = accepted_offer[0].partner_id + else: + line.selling_price = 0 + selling_price = fields.Float('Selling Price', compute="_compute_selling_price", readonly=True, copy=False) + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError("You cannot cancel a sold property.") + else: + record.state = 'cancelled' + return True + + def action_sold(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError("You cannot mark a cancelled property as sold.") + else: + record.state = 'sold' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e081984a1de..3c1345eccb6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import api, fields, models, exceptions from datetime import timedelta @@ -32,10 +32,24 @@ def _inverse_date_deadline(self): for offer in self: if offer.date_deadline: if offer.create_date: - offer.validity = (offer.date_deadline - offer.create_date).days + offer.validity = offer.date_deadline.day - offer.create_date.day else: - offer.validity = (offer.date_deadline - fields.Date.today()).days + offer.validity = (offer.date_deadline - fields.Date.today()).day else: offer.validity = 0 date_deadline = fields.Date("Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True) + + def action_accept(self): + for offer in self: + existing_accepted = self.env['estate.property.offer'].search([ + ('property_id', '=', offer.property_id.id), + ('status', '=', 'accepted') + ]) + if existing_accepted: + raise exceptions.UserError("An offer for this property has already been accepted.") + offer.status = 'accepted' + + def action_refuse(self): + for offer in self: + offer.status = 'refused' diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 2097a5cccec..846f2fce9aa 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -26,6 +26,8 @@ + + +

+ +

+ + + + + + + + + + + +
+ + estate.property.type.list + estate.property.type + + + + + + + + Property Types estate.property.type diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a6f6a230c06..4fad7e48b02 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -8,7 +8,7 @@ - +
-
- + - @@ -59,14 +59,14 @@ - - + +
- + @@ -85,14 +85,14 @@ estate.property.list estate.property - + - + @@ -102,5 +102,7 @@ Properties estate.property list,form + + {'search_default_available_properties': True} From c585ab13fa3b2134f42c2a83b0c8423ef6cf2ef5 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Wed, 29 Apr 2026 11:40:56 +0200 Subject: [PATCH 14/17] [ADD] estate: Add the sprinkles Added lots of sprinkles --- estate/models/estate_property.py | 2 +- estate/tests/__init__.py | 4 ---- estate/views/estate_property_views.xml | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index db0e4a111b0..199cf1981b7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,7 +9,7 @@ class EstateProperty(models.Model): name = fields.Char('Property Name', required=True) description = fields.Text() property_type_id = fields.Many2one( - 'estate.property.type', option="{'no_quick_create': true}") + 'estate.property.type') buyer_id = fields.Many2one('res.partner', copy=False) salesperson_id = fields.Many2one( 'res.users', default=lambda self: self.env.uid) diff --git a/estate/tests/__init__.py b/estate/tests/__init__.py index bc9a4d05939..576617cccff 100644 --- a/estate/tests/__init__.py +++ b/estate/tests/__init__.py @@ -1,5 +1 @@ -<<<<<<< HEAD from . import test_estate_property -======= -from . import test_estate_property ->>>>>>> 62b91f19a ([ADD] estate: Add the sprinkles) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4fad7e48b02..3c15cb4ffbe 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -40,7 +40,7 @@ - + From 5d80f2901db1175cffbb540bd46798794dedfe76 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Wed, 29 Apr 2026 16:39:24 +0200 Subject: [PATCH 15/17] [ADD] estate: Inheritance Added business logic to CRUD methods Added property field and views to users --- estate/__manifest__.py | 1 + estate/models/__init__.py | 2 +- estate/models/estate_property.py | 7 +++++++ estate/models/estate_property_offer.py | 14 +++++++++----- estate/models/res_users.py | 8 ++++++++ estate/views/res_users.xml | 14 ++++++++++++++ 6 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index f302c9b1e9a..a436ecaca90 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -22,6 +22,7 @@ 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_menus.xml', + 'views/res_users.xml', ], 'license': 'LGPL-3', } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 93a6bd86abd..79238789c22 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer +from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 199cf1981b7..396356f5eb0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -112,3 +112,10 @@ def _onchange_garden(self): else: self.garden_area = 10 self.garden_orientation = 'north' + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_cancelled(self): + for record in self: + if record.state not in ['new', 'cancelled']: + raise exceptions.UserError( + "You cannot delete a property that is not 'New' or 'Cancelled'!") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4f8ee58bdb3..a4e403040fc 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -46,12 +46,16 @@ def _inverse_date_deadline(self): @api.model_create_multi def create(self, vals_list): - offers = super().create(vals_list) - for offer in offers: - if offer.property_id.state == 'new': - offer.property_id.state = 'offer received' + for vals in vals_list: + prop = self.env['estate.property'].browse(vals['property_id']) + if prop.offer_ids: + max_offer = max(prop.offer_ids.mapped('price')) + if vals.get('price') < max_offer: + raise exceptions.UserError( + ("The offer must be higher than %.2f") % max_offer) + prop.state = 'offer received' - return offers + return super().create(vals_list) def action_accept(self): for offer in self: diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..8b30c3a5567 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + 'estate.property', 'salesperson_id') diff --git a/estate/views/res_users.xml b/estate/views/res_users.xml new file mode 100644 index 00000000000..b4fa40491d1 --- /dev/null +++ b/estate/views/res_users.xml @@ -0,0 +1,14 @@ + + + res.users.form + res.users + + + + + + + + + + From 60938a8d7db41469ede66beaafb873e34491e446 Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Thu, 30 Apr 2026 11:20:35 +0200 Subject: [PATCH 16/17] [ADD] estate_account: creation and link with estate Create estate_account module Link with estate module for invoice creation --- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 17 ++++++++++++++ estate_account/data/ir.model.access.csv | 1 + estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 30 ++++++++++++++++++++++++ 5 files changed, 50 insertions(+) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/data/ir.model.access.csv create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..ce27f3d4b7c --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': "Estate Account", + 'author': "Odoo", + 'website': "https://www.odoo.com/", + 'category': 'Tutorials', + 'version': '19.0.0.1.0', + 'application': True, + 'depends': [ + 'base', + 'account', + 'estate', + ], + 'license': 'LGPL-3', + 'data': [ + 'data/ir.model.access.csv', + ], +} diff --git a/estate_account/data/ir.model.access.csv b/estate_account/data/ir.model.access.csv new file mode 100644 index 00000000000..97dd8b917b8 --- /dev/null +++ b/estate_account/data/ir.model.access.csv @@ -0,0 +1 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..a599f20886f --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,30 @@ +from odoo import models, Command + + +class InheritedModel(models.Model): + _inherit = "estate.property" + + def action_sold(self): + res = super().action_sold() + for record in self: + commission_amount = record.selling_price * 0.06 + self.env['account.move'].create({ + 'partner_id': record.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + # Line 1: 6% Commission + Command.create({ + "name": f"6% Commission for {record.name}", + "quantity": 1.0, + "price_unit": commission_amount, + }), + # Line 2: Administrative Fees + Command.create({ + "name": "Administrative Fees", + "quantity": 1.0, + "price_unit": 100.0, + }), + ], + }) + + return res From a320c8f889e3f4e4adf6aa1d024123254a97a8ba Mon Sep 17 00:00:00 2001 From: simal-odoo Date: Thu, 30 Apr 2026 14:06:03 +0200 Subject: [PATCH 17/17] [ADD] estate: creation of kanban view Create a kanban view where properties are sorted by type --- estate/views/estate_property_views.xml | 35 +++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3c15cb4ffbe..095dad59bff 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -98,10 +98,43 @@ + + estate.property.kanban + estate.property + + + + + +
+
+ + + +
+
+ Expected Price: +
+
+ Best Offer: +
+
+ Selling Price: +
+
+ +
+
+
+
+
+
+
+ Properties estate.property - list,form + kanban,list,form {'search_default_available_properties': True}