From 0a1b5d447ee793a03a7e118992dd9761316c60c9 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Mon, 20 Oct 2025 13:54:46 +0200 Subject: [PATCH 01/23] [ADD] estate: Create the app --- estate/__init__.py | 0 estate/__manifest__.py | 10 ++++++++++ 2 files changed, 10 insertions(+) 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..632e201f4ff --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': 'Real Estate', + 'version': '1.0', + 'depends': ['base'], + 'author': 'Your Name', + 'category': 'Real Estate', + 'summary': 'A basic real estate module', + 'description': 'A tutorial module for real estate management', + 'application': True, +} From 7c751ec206fde4282c398e72bfde5740f48bc2b3 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Mon, 20 Oct 2025 13:57:58 +0200 Subject: [PATCH 02/23] [IMP] estate: change the module's author name to my name --- estate/__manifest__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 632e201f4ff..859bd9eed08 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,7 @@ 'name': 'Real Estate', 'version': '1.0', 'depends': ['base'], - 'author': 'Your Name', + 'author': 'Mohamed (moaln)', 'category': 'Real Estate', 'summary': 'A basic real estate module', 'description': 'A tutorial module for real estate management', From ea0ef151bc09f7df08038635386e72618f24b9db Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Mon, 20 Oct 2025 15:57:09 +0200 Subject: [PATCH 03/23] [IMP] estate: added the estate property model --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 21 +++++++++++++++++++++ 3 files changed, 23 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/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..c490d88eed3 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,21 @@ +from odoo import models, fields + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "estate property model" + + name = fields.Char(required = True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required = True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] + ) From 86659ebba4af56d73d26b9ca9893b2c7e1688840 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Mon, 20 Oct 2025 16:00:26 +0200 Subject: [PATCH 04/23] [IMP] estate: added access rights --- estate/__manifest__.py | 6 ++++-- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 859bd9eed08..6cab9c0caad 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,10 +1,12 @@ { 'name': 'Real Estate', + 'description': 'A tutorial module for real estate management', 'version': '1.0', 'depends': ['base'], - 'author': 'Mohamed (moaln)', 'category': 'Real Estate', 'summary': 'A basic real estate module', - 'description': 'A tutorial module for real estate management', + 'data': ['security/ir.model.access.csv'], 'application': True, + 'author': 'Mohamed (moaln)', + 'license': 'LGPL-3', } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From 405330c882308ca69ff6308dae48521a92f8c143 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Tue, 21 Oct 2025 13:43:27 +0200 Subject: [PATCH 05/23] [IMP] estate: created menus, actions and customized model fields --- estate/__init__.py | 3 ++- estate/__manifest__.py | 6 +++++- estate/models/estate_property.py | 18 +++++++++++++----- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 8 ++++++++ 5 files changed, 35 insertions(+), 7 deletions(-) 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..a82c623203c 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ -from . import models \ No newline at end of file +from . import models +from . import views \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 6cab9c0caad..3e81cd361a3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,7 +5,11 @@ 'depends': ['base'], 'category': 'Real Estate', 'summary': 'A basic real estate module', - 'data': ['security/ir.model.access.csv'], + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' + ], 'application': True, 'author': 'Mohamed (moaln)', 'license': 'LGPL-3', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c490d88eed3..de2f91597a7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,16 +1,17 @@ from odoo import models, fields +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): _name = "estate.property" _description = "estate property model" - name = fields.Char(required = True) + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() - expected_price = fields.Float(required = True) - selling_price = fields.Float() - bedrooms = fields.Integer() + date_availability = fields.Date(copy=False, default=fields.Date.today() + relativedelta(months =+ 3)) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -19,3 +20,10 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] ) + active = fields.Boolean(default=True) + state = fields.Selection( + selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')], + required=True, + copy=False, + default='new' + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..ad1596cebcf --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..1d2a3aaa4cd --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate.property + list,form + + From e7792be7aa5ffa9d393857a2a66c6a7163fb16d2 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Tue, 21 Oct 2025 15:58:56 +0200 Subject: [PATCH 06/23] [IMP] estate: added list, menu and search views --- estate/__init__.py | 2 +- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 3 +- estate/views/estate_property_views.xml | 71 ++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index a82c623203c..3b1476bbd28 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1,2 @@ from . import models -from . import views \ No newline at end of file +from . import views 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 de2f91597a7..77d9c1657a7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from odoo import models, fields from dateutil.relativedelta import relativedelta + class EstateProperty(models.Model): _name = "estate.property" _description = "estate property model" @@ -8,7 +9,7 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=fields.Date.today() + relativedelta(months =+ 3)) + date_availability = fields.Date(copy=False, default=fields.Date.today() + relativedelta(months=+3)) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1d2a3aaa4cd..9ecd281ff52 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,76 @@ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + estate.property.menu + estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + estate.property.list + estate.property + + + + + + + + + + + + + Properties estate.property From 37a5c08ac0c87ea5f6601283838303713180c692 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Tue, 21 Oct 2025 16:08:36 +0200 Subject: [PATCH 07/23] [FIX] estate: added the missing name field in the form view --- estate/views/estate_property_views.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9ecd281ff52..18f3edce429 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -24,7 +24,12 @@ estate.property
- + +
+

+ +

+
From fc5087848fb9912466c96d99b4db93c6a0e52b25 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Wed, 22 Oct 2025 10:11:01 +0200 Subject: [PATCH 08/23] [IMP] estate: added Many2one, Many2many and One2many relations --- estate/__init__.py | 1 - estate/__manifest__.py | 3 ++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 5 +++ estate/models/estate_property_offer.py | 14 +++++++ 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 | 8 +++- estate/views/estate_property_offer_views.xml | 40 ++++++++++++++++++ estate/views/estate_property_tag_views.xml | 44 ++++++++++++++++++++ estate/views/estate_property_type_views.xml | 44 ++++++++++++++++++++ estate/views/estate_property_views.xml | 18 +++++++- 13 files changed, 196 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/__init__.py b/estate/__init__.py index 3b1476bbd28..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1 @@ from . import models -from . import views diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3e81cd361a3..30d0862ff1d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_menus.xml' ], 'application': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..09b2099fe84 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 \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 77d9c1657a7..fef89d8db52 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -28,3 +28,8 @@ class EstateProperty(models.Model): copy=False, default='new' ) + estate_property_type_id = fields.Many2one("estate.property.type", string="Property Type") + buyer_id = fields.Many2one("res.partner", copy=False) + salesperson_id = fields.Many2one("res.users", default=lambda self: self.env.user) + estate_property_tag_ids = fields.Many2many("estate.property.tag", string="Propert 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..4f14ff79e05 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,14 @@ +from odoo import models, fields + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "estate property offer" + + price = fields.Float() + 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..ecdf61cb119 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "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..172d1c93c35 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "estate property type" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..89f97c50842 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,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index ad1596cebcf..65fdac8aeff 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,11 @@ - - + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..73a06ef7bb0 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,40 @@ + + + + estate.property.offer.search + estate.property.offer + + + + + + + + + estate.property.offer.menu + 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..115df734e05 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,44 @@ + + + + estate.property.tag.search + estate.property.tag + + + + + + + + + estate.property.tag.menu + estate.property.tag + +
+ +
+

+ +

+
+
+
+
+
+ + + estate.property.tag.list + 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..4adf5c919c5 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,44 @@ + + + + estate.property.type.search + estate.property.type + + + + + + + + + estate.property.type.menu + estate.property.type + +
+ +
+

+ +

+
+
+
+
+
+ + + estate.property.type.list + 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 18f3edce429..7810d41b03b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -11,6 +11,7 @@ + @@ -29,9 +30,13 @@

- + + + + + @@ -54,6 +59,15 @@ + + + + + + + + +
@@ -66,6 +80,8 @@ + + From 9fd358419b6038a9389ed8e764dc409f5761077c Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Wed, 22 Oct 2025 11:46:39 +0200 Subject: [PATCH 09/23] [IMP] estate: added computed fields and onchanges --- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 24 +++++++++++++++++++- estate/models/estate_property_offer.py | 15 +++++++++++- estate/views/estate_property_offer_views.xml | 4 ++++ estate/views/estate_property_views.xml | 9 ++++---- 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe84..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,4 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index fef89d8db52..f4336107c0b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import api, models, fields from dateutil.relativedelta import relativedelta @@ -33,3 +33,25 @@ class EstateProperty(models.Model): salesperson_id = fields.Many2one("res.users", default=lambda self: self.env.user) estate_property_tag_ids = fields.Many2many("estate.property.tag", string="Propert Tags") offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + + total_area = fields.Float(compute="_compute_total_area") + best_price = fields.Float(compute="_compute_best_price") + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for property in self: + property.total_area = property.living_area + property.garden_area + + @api.depends('offer_ids') + def _compute_best_price(self): + for property in self: + property.best_price = max(property.offer_ids.mapped('price') or [0]) + + @api.onchange('garden') + def _onchange_garden(self): + if(self.garden): + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = False + self.garden_orientation = "" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4f14ff79e05..37a5ea400d3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import models, fields +from odoo import api, models, fields +from dateutil.relativedelta import relativedelta class EstatePropertyOffer(models.Model): @@ -12,3 +13,15 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one('res.partner', string="Partner", required=True) property_id = fields.Many2one('estate.property', string="Property", required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + @api.depends("validity", "create_date") + def _compute_date_deadline(self): + for offer in self: + offer.date_deadline = (offer.create_date or fields.Date.today()) + relativedelta(days=+offer.validity) + + def _inverse_date_deadline(self): + for offer in self: + offer.validity = (offer.date_deadline - (offer.create_date or fields.Date.today()).date()).days + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 73a06ef7bb0..ea497cdbc77 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -19,6 +19,8 @@ + +
@@ -33,6 +35,8 @@ + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7810d41b03b..5e2459e29e0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -20,7 +20,7 @@
- + estate.property.menu estate.property @@ -31,9 +31,7 @@ - - - + @@ -42,6 +40,7 @@ + @@ -56,7 +55,7 @@ - + From 872c4a747b5e08881f1b3557c7ecae48ba379c18 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Wed, 22 Oct 2025 11:49:51 +0200 Subject: [PATCH 10/23] [FIX] estate: fix style errors --- estate/models/estate_property.py | 2 +- estate/models/estate_property_offer.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f4336107c0b..de2bb15f4ce 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -49,7 +49,7 @@ def _compute_best_price(self): @api.onchange('garden') def _onchange_garden(self): - if(self.garden): + if (self.garden): self.garden_area = 10 self.garden_orientation = "north" else: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 37a5ea400d3..9f59dcef430 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -24,4 +24,3 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for offer in self: offer.validity = (offer.date_deadline - (offer.create_date or fields.Date.today()).date()).days - From b9d83dd197cf3225bddc38d27e42fb0e4bd154d3 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Wed, 22 Oct 2025 13:54:05 +0200 Subject: [PATCH 11/23] [IMP] estate: added action buttons --- estate/models/estate_property.py | 15 +++++++++++++++ estate/models/estate_property_offer.py | 19 +++++++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 5 +++++ 4 files changed, 41 insertions(+) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index de2bb15f4ce..ca0bd7b6bb4 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from odoo import api, models, fields from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -55,3 +56,17 @@ def _onchange_garden(self): else: self.garden_area = False self.garden_orientation = "" + + def action_mark_sold(self): + for property in self: + if property.state == "cancelled": + raise UserError("You cann't sell a cancelled property") + property.state = "sold" + return True + + def action_mark_cancel(self): + for property in self: + if property.state == "sold": + raise UserError("You cann't cancell a sold property") + property.state = "cancelled" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9f59dcef430..9667e2510a3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from odoo import api, models, fields from dateutil.relativedelta import relativedelta +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -24,3 +25,21 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for offer in self: offer.validity = (offer.date_deadline - (offer.create_date or fields.Date.today()).date()).days + + def accept_offer(self): + for offer in self: + if offer.status == 'refused': + raise UserError("You cann't accept a refused offer") + if offer.property_id.buyer_id: + raise UserError("Only one offer can be accepted") + offer.property_id.buyer_id = offer.partner_id + offer.property_id.selling_price = offer.price + offer.status = "accepted" + return True + + def refuse_offer(self): + for offer in self: + if offer.status == 'accepted': + raise UserError("You cann't refuse an accepted offer") + offer.status = "refused" + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index ea497cdbc77..409ab310ed4 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -37,6 +37,8 @@ + +

+ + + + + + + + + + +
@@ -21,6 +36,7 @@ estate.property.type + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 93aab5cceb0..881c500caba 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -9,11 +9,12 @@ - + - - - + + +
@@ -24,8 +25,9 @@
-
@@ -33,10 +35,11 @@
- + - + @@ -55,14 +58,15 @@ - - + + - + @@ -80,16 +84,18 @@ estate.property.list estate.property - + - - + + - + @@ -98,5 +104,6 @@ Properties estate.property list,form + {'search_default_available': True} From 6073b1960a167c3af6cd143699f88b29419c0321 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Thu, 23 Oct 2025 11:18:05 +0200 Subject: [PATCH 15/23] [FIX] estate: fix style and dependency errors --- estate/__manifest__.py | 2 +- estate/models/estate_property_type.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 5e6e52ba846..953f7bf3906 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,9 +8,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', - 'views/estate_property_offer_views.xml', 'views/estate_menus.xml' ], 'application': True, diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index d94e46e3529..7c017e08a76 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -20,4 +20,4 @@ class EstatePropertyType(models.Model): @api.depends("offer_ids") def _compute_offer_count(self): for type in self: - type.offer_count = len(type.offer_ids or []) \ No newline at end of file + type.offer_count = len(type.offer_ids or []) From 44589e3147e8ec9ad6038526df0284501ad477db Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Thu, 23 Oct 2025 14:40:01 +0200 Subject: [PATCH 16/23] [IMP] estate: added inheritance --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 6 ++++++ estate/models/estate_property_offer.py | 11 ++++++++++- estate/models/user_with_properties.py | 9 +++++++++ estate/views/estate_property_views.xml | 6 ++++-- estate/views/user_with_properties_views.xml | 14 ++++++++++++++ 7 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 estate/models/user_with_properties.py create mode 100644 estate/views/user_with_properties_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 953f7bf3906..b7555af50f9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,7 @@ 'summary': 'A basic real estate module', 'data': [ 'security/ir.model.access.csv', + 'views/user_with_properties_views.xml', 'views/estate_property_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..6f1b4f2fe31 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import user_with_properties \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1846d6239e5..9898cdd525e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -83,6 +83,12 @@ def _onchange_garden(self): self.garden_area = False self.garden_orientation = "" + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_cancelled_state(self): + if any(not (property.state == 'new' or property.state == 'cancelled') for property in self): + raise UserError("You can only delete new or cancelled properties") + + def action_mark_sold(self): for property in self: if property.state == "cancelled": diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 0ccd007a2aa..2cd884e59ba 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -14,7 +14,7 @@ class EstatePropertyOffer(models.Model): copy=False ) partner_id = fields.Many2one('res.partner', string="Partner", required=True) - property_id = fields.Many2one('estate.property', string="Property", required=True) + property_id = fields.Many2one('estate.property', string="Property", required=True, ondelete="cascade") validity = fields.Integer(default=7) date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") property_state = fields.Selection(related="property_id.state") @@ -34,6 +34,15 @@ def _inverse_date_deadline(self): for offer in self: offer.validity = (offer.date_deadline - (offer.create_date or fields.Date.today()).date()).days + @api.model + def create(self, vals): + for offer in vals: + property = self.env['estate.property'].browse(offer['property_id']) + property.state = "offer_received" + if offer['price'] < property.best_price: + raise UserError(f"The offer must be higher than {property.best_price}") + return super().create(vals) + def accept_offer(self): for offer in self: if offer.status == 'refused': diff --git a/estate/models/user_with_properties.py b/estate/models/user_with_properties.py new file mode 100644 index 00000000000..37c164314a4 --- /dev/null +++ b/estate/models/user_with_properties.py @@ -0,0 +1,9 @@ +from odoo import models, fields + + +class UserWithProperties(models.Model): + _inherit = 'res.users' + _description = 'user with properties model' + + property_ids = fields.One2many('estate.property', 'salesperson_id', string='Properties', + domain=['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 881c500caba..2cbb20233d3 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -13,7 +13,8 @@ filter_domain="['!', ('living_area', '<' , self)]"/> - + @@ -95,7 +96,8 @@ - + diff --git a/estate/views/user_with_properties_views.xml b/estate/views/user_with_properties_views.xml new file mode 100644 index 00000000000..0299a964006 --- /dev/null +++ b/estate/views/user_with_properties_views.xml @@ -0,0 +1,14 @@ + + + user.with.properties.list + res.users + + + + + + + + + + \ No newline at end of file From ae88eebc205dde41dcfdc3977b51e154149261cf Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Thu, 23 Oct 2025 14:51:26 +0200 Subject: [PATCH 17/23] [FIX] estate: fix style errors --- estate/__manifest__.py | 2 +- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 1 - estate/views/estate_property_views.xml | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b7555af50f9..45c44489263 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,11 +7,11 @@ 'summary': 'A basic real estate module', 'data': [ 'security/ir.model.access.csv', - 'views/user_with_properties_views.xml', 'views/estate_property_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', + 'views/user_with_properties_views.xml', 'views/estate_menus.xml' ], 'application': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 6f1b4f2fe31..6820429cbbf 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,4 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer -from . import user_with_properties \ No newline at end of file +from . import user_with_properties diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9898cdd525e..c9c8ed84f8b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -88,7 +88,6 @@ def _unlink_if_new_or_cancelled_state(self): if any(not (property.state == 'new' or property.state == 'cancelled') for property in self): raise UserError("You can only delete new or cancelled properties") - def action_mark_sold(self): for property in self: if property.state == "cancelled": diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 2cbb20233d3..20a25934934 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -96,8 +96,8 @@ - + + From 335c155738311a6c03ee2434b61d23c4f2603f07 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Thu, 23 Oct 2025 16:01:23 +0200 Subject: [PATCH 18/23] [CLN] estate: formatted the code with pycharm --- estate/models/estate_property.py | 30 ++++++++++---------- estate/models/estate_property_offer.py | 10 +++---- estate/views/estate_menus.xml | 6 ++-- estate/views/estate_property_offer_views.xml | 10 +++---- estate/views/estate_property_views.xml | 30 +++++++++++--------- estate/views/user_with_properties_views.xml | 4 +-- 6 files changed, 47 insertions(+), 43 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c9c8ed84f8b..453eda53e95 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -58,25 +58,25 @@ class EstateProperty(models.Model): @api.constrains('expected_price', 'selling_price') def _check_selling_price(self): - for property in self: - percentage = property.selling_price / property.expected_price - if not float_is_zero(property.selling_price, precision_digits=2) \ - and float_compare(percentage, 0.9, precision_digits=2) == -1: + for prop in self: + percentage = prop.selling_price / prop.expected_price + if not float_is_zero(prop.selling_price, precision_digits=2) \ + and float_compare(percentage, 0.9, precision_digits=2) == -1: raise UserError("selling price cannot be lower than 90% of the expected price") @api.depends('living_area', 'garden_area') def _compute_total_area(self): - for property in self: - property.total_area = property.living_area + property.garden_area + for prop in self: + prop.total_area = prop.living_area + prop.garden_area @api.depends('offer_ids') def _compute_best_price(self): - for property in self: - property.best_price = max(property.offer_ids.mapped('price') or [0]) + for prop in self: + prop.best_price = max(prop.offer_ids.mapped('price') or [0]) @api.onchange('garden') def _onchange_garden(self): - if (self.garden): + if self.garden: self.garden_area = 10 self.garden_orientation = "north" else: @@ -89,15 +89,15 @@ def _unlink_if_new_or_cancelled_state(self): raise UserError("You can only delete new or cancelled properties") def action_mark_sold(self): - for property in self: - if property.state == "cancelled": + for prop in self: + if prop.state == "cancelled": raise UserError("You cann't sell a cancelled property") - property.state = "sold" + prop.state = "sold" return True def action_mark_cancel(self): - for property in self: - if property.state == "sold": + for prop in self: + if prop.state == "sold": raise UserError("You cann't cancell a sold property") - property.state = "cancelled" + prop.state = "cancelled" return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 2cd884e59ba..e115638f2f3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -37,10 +37,10 @@ def _inverse_date_deadline(self): @api.model def create(self, vals): for offer in vals: - property = self.env['estate.property'].browse(offer['property_id']) - property.state = "offer_received" - if offer['price'] < property.best_price: - raise UserError(f"The offer must be higher than {property.best_price}") + prop = self.env['estate.property'].browse(offer['property_id']) + prop.state = "offer_received" + if offer['price'] < prop.best_price: + raise UserError(f"The offer must be higher than {prop.best_price}") return super().create(vals) def accept_offer(self): @@ -58,6 +58,6 @@ def accept_offer(self): def refuse_offer(self): for offer in self: if offer.status == 'accepted': - raise UserError("You cann't refuse an accepted offer") + raise UserError("You cannot refuse an accepted offer") offer.status = "refused" return True diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index b4c46d952ba..ad7f6bdaeec 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -5,8 +5,10 @@ - - + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 06a9e0bbe44..aa54ffbd32b 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -23,15 +23,15 @@ estate.property.offer + decoration-danger="status == 'refused'" decoration-success="status == 'accepted'"> - + class="oe_stat_button" icon="fa-star"/>
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 70436e37456..f2cedc8c040 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -26,9 +26,9 @@
-
@@ -113,7 +113,11 @@
- +
+

+ +

+
Expected Price:
@@ -125,7 +129,10 @@ Selling Price:
- +
+ +
diff --git a/estate/views/user_with_properties_views.xml b/estate/views/res_users_views.xml similarity index 77% rename from estate/views/user_with_properties_views.xml rename to estate/views/res_users_views.xml index 274ef30ee35..4d499796687 100644 --- a/estate/views/user_with_properties_views.xml +++ b/estate/views/res_users_views.xml @@ -1,6 +1,6 @@ - - user.with.properties.list + + res.users.view.form.inherit.estate res.users diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 91b0021992e..a5c9be0dc9f 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -4,7 +4,8 @@ class EstateProperty(models.Model): _inherit = "estate.property" - def action_mark_sold(self): + def action_sold(self): + super().action_sold() for prop in self: self.env['account.move'].sudo().create( { @@ -24,4 +25,3 @@ def action_mark_sold(self): ] } ) - return super().action_mark_sold() From e6b35b960b0edaa94189570cda13ef34fca93dc4 Mon Sep 17 00:00:00 2001 From: "Mohamed (moaln)" Date: Tue, 28 Oct 2025 15:57:06 +0100 Subject: [PATCH 23/23] [CLN] estate{_account}: applied suggested review comments --- estate/models/__init__.py | 4 +- estate/models/estate_property.py | 96 ++++++++++++-------- estate/models/estate_property_offer.py | 67 ++++++++------ estate/models/estate_property_tag.py | 9 +- estate/models/estate_property_type.py | 17 ++-- estate/models/res_users.py | 20 +++- estate/views/estate_menus.xml | 5 - estate/views/estate_property_offer_views.xml | 8 +- estate/views/estate_property_views.xml | 14 +-- estate_account/__manifest__.py | 15 ++- estate_account/models/estate_property.py | 37 ++++---- 11 files changed, 167 insertions(+), 125 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 9a2189b6382..fea9f441d6d 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,5 +1,5 @@ from . import estate_property -from . import estate_property_type -from . import estate_property_tag from . import estate_property_offer +from . import estate_property_tag +from . import estate_property_type from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index dd3ff5a41ed..475bfc98bfb 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from dateutil.relativedelta import relativedelta -from odoo import api, models, fields + +from odoo import api, fields, models from odoo.exceptions import UserError from odoo.tools.float_utils import float_compare, float_is_zero @@ -12,7 +13,9 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=fields.Date.today() + relativedelta(months=+3)) + date_availability = fields.Date( + copy=False, default=fields.Date.today() + relativedelta(months=+3) + ) expected_price = fields.Float(required=True, default=1) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -22,78 +25,91 @@ class EstateProperty(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] ) active = fields.Boolean(default=True) state = fields.Selection( selection=[ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled') + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), ], required=True, copy=False, - default='new' + default="new", ) property_type_id = fields.Many2one("estate.property.type", string="Property Type") buyer_id = fields.Many2one("res.partner", copy=False) salesperson_id = fields.Many2one("res.users", default=lambda self: self.env.user) property_tag_ids = fields.Many2many("estate.property.tag", string="Propert Tags") - offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") - + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") total_area = fields.Float(compute="_compute_total_area") best_price = fields.Float(compute="_compute_best_price") - - _positive_expected_price = models.Constraint( - 'CHECK(expected_price > 0)', - 'The Expected Price of a Property must be strictly positive' + _check_positive_expected_price = models.Constraint( + "CHECK(expected_price > 0)", + "The Expected Price of a Property must be strictly positive", ) - - _positive_selling_price = models.Constraint( - 'CHECK(selling_price > 0)', - 'The Selling Price of a Property must be strictly positive' + _check_positive_selling_price = models.Constraint( + "CHECK(selling_price > 0)", + "The Selling Price of a Property must be strictly positive", ) - @api.constrains('expected_price', 'selling_price') + @api.constrains("expected_price", "selling_price") def _check_selling_price(self): - for prop in self: - percentage = prop.selling_price / prop.expected_price - if not float_is_zero(prop.selling_price, precision_digits=2) \ - and float_compare(percentage, 0.9, precision_digits=2) == -1: - raise UserError("selling price cannot be lower than 90% of the expected price") + for property in self: + percentage = property.selling_price / property.expected_price + if ( + not float_is_zero(property.selling_price, precision_digits=2) + and float_compare(percentage, 0.9, precision_digits=2) == -1 + ): + raise UserError( + "selling price cannot be lower than 90% of the expected price" + ) - @api.depends('living_area', 'garden_area') + @api.depends("living_area", "garden_area") def _compute_total_area(self): - for prop in self: - prop.total_area = prop.living_area + prop.garden_area + self.ensure_one() + for property in self: + property.total_area = property.living_area + property.garden_area - @api.depends('offer_ids') + @api.depends("offer_ids") def _compute_best_price(self): - for prop in self: - prop.best_price = max(prop.offer_ids.mapped('price') or [0]) + self.ensure_one() + for property in self: + property.best_price = max(property.offer_ids.mapped("price") or [0]) - @api.onchange('garden') + @api.onchange("garden") def _onchange_garden(self): self.garden_area = 10 if self.garden else 0 - self.garden_orientation = 'north' if self.garden else '' + self.garden_orientation = "north" if self.garden else "" @api.ondelete(at_uninstall=False) def _unlink_if_new_or_cancelled_state(self): - if any(not (prop.state == 'new' or prop.state == 'cancelled') for prop in self): + if any( + not (property.state == "new" or property.state == "cancelled") + for property in self + ): raise UserError("You can only delete new or cancelled properties") def action_sold(self): - for prop in self: - if prop.state == "cancelled": + self.ensure_one() + for property in self: + if property.state == "cancelled": raise UserError("You cannot sell a cancelled property") - prop.state = "sold" + property.state = "sold" return True def action_cancel(self): - for prop in self: - if prop.state == "sold": + self.ensure_one() + for property in self: + if property.state == "sold": raise UserError("You cannot cancel a sold property") - prop.state = "cancelled" + property.state = "cancelled" return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e115638f2f3..f4c7e754b7d 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from dateutil.relativedelta import relativedelta -from odoo import api, models, fields + +from odoo import api, fields, models from odoo.exceptions import UserError @@ -10,43 +11,56 @@ class EstatePropertyOffer(models.Model): price = fields.Float() status = fields.Selection( - selection=[('accepted', 'Accepted'), ('refused', 'Refused')], - copy=False + selection=[("accepted", "Accepted"), ("refused", "Refused")], copy=False + ) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one( + "estate.property", string="Property", required=True, ondelete="cascade" ) - partner_id = fields.Many2one('res.partner', string="Partner", required=True) - property_id = fields.Many2one('estate.property', string="Property", required=True, ondelete="cascade") validity = fields.Integer(default=7) - date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + date_deadline = fields.Date( + compute="_compute_date_deadline", inverse="_inverse_date_deadline" + ) property_state = fields.Selection(related="property_id.state") - property_type_id = fields.Many2one(related="property_id.property_type_id", store=True) - - _positive_offer_price = models.Constraint( - 'CHECK(price > 0)', - 'The Offer Price must be strictly positive' + property_type_id = fields.Many2one( + related="property_id.property_type_id", store=True + ) + _check_positive_offer_price = models.Constraint( + "CHECK(price > 0)", "The Offer Price must be strictly positive" ) @api.depends("validity", "create_date") def _compute_date_deadline(self): + self.ensure_one() for offer in self: - offer.date_deadline = (offer.create_date or fields.Date.today()) + relativedelta(days=+offer.validity) + offer.date_deadline = ( + offer.create_date or fields.Date.today() + ) + relativedelta(days=+offer.validity) def _inverse_date_deadline(self): + self.ensure_one() for offer in self: - offer.validity = (offer.date_deadline - (offer.create_date or fields.Date.today()).date()).days + offer.validity = ( + offer.date_deadline - (offer.create_date or fields.Date.today()).date() + ).days @api.model - def create(self, vals): - for offer in vals: - prop = self.env['estate.property'].browse(offer['property_id']) - prop.state = "offer_received" - if offer['price'] < prop.best_price: - raise UserError(f"The offer must be higher than {prop.best_price}") - return super().create(vals) - - def accept_offer(self): + def create(self, vals_list): + EstateProperties = self.env["estate.property"].with_prefetch( + [vals["property_id"] for vals in vals_list] + ) + for offer in vals_list: + property = EstateProperties.browse(offer["property_id"]) + property.state = "offer_received" + if offer["price"] < property.best_price: + raise UserError(f"The offer must be higher than {property.best_price}") + return super().create(vals_list) + + def action_accept_offer(self): + self.ensure_one() for offer in self: - if offer.status == 'refused': - raise UserError("You cann't accept a refused offer") + if offer.status == "refused": + raise UserError("You cannot accept a refused offer") if offer.property_id.buyer_id: raise UserError("Only one offer can be accepted") offer.property_id.buyer_id = offer.partner_id @@ -55,9 +69,10 @@ def accept_offer(self): offer.status = "accepted" return True - def refuse_offer(self): + def action_refuse_offer(self): + self.ensure_one() for offer in self: - if offer.status == 'accepted': + if offer.status == "accepted": raise UserError("You cannot refuse an accepted offer") offer.status = "refused" return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 09764903ba3..09777e821f7 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import fields, models class EstatePropertyTag(models.Model): @@ -8,8 +8,7 @@ class EstatePropertyTag(models.Model): name = fields.Char(required=True) color = fields.Integer() - - _unique_name = models.Constraint( - 'UNIQUE(name)', - 'The name must be unique', + _check_unique_name = models.Constraint( + "UNIQUE(name)", + "The name must be unique", ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 7c017e08a76..f2d9e0b01cc 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import api, models, fields +from odoo import api, fields, models class EstatePropertyType(models.Model): @@ -8,13 +8,16 @@ class EstatePropertyType(models.Model): name = fields.Char(required=True) sequence = fields.Integer(default=1) - estate_property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") - offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") + estate_property_ids = fields.One2many( + "estate.property", "property_type_id", string="Properties" + ) + offer_ids = fields.One2many( + "estate.property.offer", "property_type_id", string="Offers" + ) offer_count = fields.Integer(compute="_compute_offer_count") - - _unique_name = models.Constraint( - 'UNIQUE(name)', - 'The name must be unique', + _check_unique_name = models.Constraint( + "UNIQUE(name)", + "The name must be unique", ) @api.depends("offer_ids") diff --git a/estate/models/res_users.py b/estate/models/res_users.py index a1a4affe24e..2f9ab720a8e 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -1,9 +1,19 @@ -from odoo import models, fields +from odoo import fields, models class ResUsers(models.Model): - _inherit = 'res.users' - _description = 'user with properties model' + _inherit = "res.users" + _description = "user with properties model" - property_ids = fields.One2many('estate.property', 'salesperson_id', string='Estate Properties', - domain=['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]) + property_ids = fields.One2many( + "estate.property", + "salesperson_id", + string="Estate Properties", + domain=[ + ( + "state", + "in", + ["new", "offer_received"], + ), + ], + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index eacf3ea873e..9370947aa5b 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,14 +4,12 @@ id="estate_property_menu_root" name="Real Estate" /> - - - - - -