From 11a9e659906aa92c56d3fc0bbf788101eefa7a0d Mon Sep 17 00:00:00 2001 From: odoo Date: Tue, 21 Apr 2026 16:05:52 +0200 Subject: [PATCH 01/19] [ADD] Chapitre 3 --- estate/__init__.py | 1 + estate/__manifest__.py | 9 +++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 21 +++++++++++++++++++++ 4 files changed, 32 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..9fe7e6c15dc --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,9 @@ +{ + 'name': 'estate', + 'depends': [ + 'base_setup' + ], + 'installable': True, + 'application': True, + +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..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..4b154c93caf --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,21 @@ +from odoo import models, fields + +class Estate_property(models.Model): + _name = "estate_property" + _description = "APP super mega trop bien" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection(string='Orientation', + selection=[('notrh', 'Notrh'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + help="The garden orientation") \ No newline at end of file From e8c454226348b0229a4f256ecad185b34555b50e Mon Sep 17 00:00:00 2001 From: odoo Date: Tue, 21 Apr 2026 16:42:27 +0200 Subject: [PATCH 02/19] [ADD] Chapitre 4 --- estate/__manifest__.py | 5 ++++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9fe7e6c15dc..1e5d74031ef 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,9 +1,12 @@ { 'name': 'estate', 'depends': [ - 'base_setup' + 'base' ], 'installable': True, 'application': True, + 'author': 'vibad', + 'data': ['security/ir.model.access.csv'] + } \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..03f4c262708 --- /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 +acces_estate_property,acces_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From ebe26b3f323d540a7b5c40b9043221e00c1ed551 Mon Sep 17 00:00:00 2001 From: odoo Date: Tue, 21 Apr 2026 17:28:10 +0200 Subject: [PATCH 03/19] [ADD] Chapitre 5 Menus --- estate/__manifest__.py | 2 +- estate/view/estate_action.xml | 8 ++++++++ estate/view/estate_property_views.xml | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 estate/view/estate_action.xml create mode 100644 estate/view/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 1e5d74031ef..2be577eb432 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,7 +6,7 @@ 'installable': True, 'application': True, 'author': 'vibad', - 'data': ['security/ir.model.access.csv'] + 'data': ['security/ir.model.access.csv', 'view/estate_property_views.xml', 'view/estate_action.xml'] } \ No newline at end of file diff --git a/estate/view/estate_action.xml b/estate/view/estate_action.xml new file mode 100644 index 00000000000..3e734eb981b --- /dev/null +++ b/estate/view/estate_action.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml new file mode 100644 index 00000000000..825ae24d7d4 --- /dev/null +++ b/estate/view/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate_property + list,form + + \ No newline at end of file From 760eba1b6e1c0d9dabb87555dbf0f6a0ba60d289 Mon Sep 17 00:00:00 2001 From: vibad Date: Wed, 22 Apr 2026 10:03:47 +0200 Subject: [PATCH 04/19] [CLN] clean style --- estate/__init__.py | 2 +- estate/__manifest__.py | 8 ++++++-- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 9 ++++++--- estate/security/ir.model.access.csv | 2 +- estate/view/estate_action.xml | 4 ++-- estate/view/estate_property_views.xml | 2 +- 7 files changed, 18 insertions(+), 11 deletions(-) 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 2be577eb432..39e60af8cc9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,7 +6,11 @@ 'installable': True, 'application': True, 'author': 'vibad', - 'data': ['security/ir.model.access.csv', 'view/estate_property_views.xml', 'view/estate_action.xml'] + 'data': [ + 'security/ir.model.access.csv', + 'view/estate_property_views.xml', + 'view/estate_action.xml', + ], -} \ No newline at end of file +} 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 4b154c93caf..a7e8421a730 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ from odoo import models, fields + class Estate_property(models.Model): _name = "estate_property" _description = "APP super mega trop bien" @@ -16,6 +17,8 @@ class Estate_property(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() - garden_orientation = fields.Selection(string='Orientation', - selection=[('notrh', 'Notrh'), ('south', 'South'), ('east', 'East'), ('west', 'West')], - help="The garden orientation") \ No newline at end of file + garden_orientation = fields.Selection( + string='Orientation', + selection=[('notrh', 'Notrh'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + help="The garden orientation" + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 03f4c262708..561d4ef4824 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -acces_estate_property,acces_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +acces_estate_property,acces_estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/view/estate_action.xml b/estate/view/estate_action.xml index 3e734eb981b..c3dde6fb10a 100644 --- a/estate/view/estate_action.xml +++ b/estate/view/estate_action.xml @@ -1,8 +1,8 @@ - + - \ No newline at end of file + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 825ae24d7d4..c2a0fc920ac 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -5,4 +5,4 @@ estate_property list,form - \ No newline at end of file + From 86dbba3ad41d268633fb04b8a7b0e96fe7ae386a Mon Sep 17 00:00:00 2001 From: vibad Date: Wed, 22 Apr 2026 11:27:58 +0200 Subject: [PATCH 05/19] [ADD] Chapitre 5 --- estate/models/estate_property.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a7e8421a730..5bb077f54a3 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,17 +8,25 @@ class Estate_property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(copy=False, default=fields.Date.add(fields.Date.today(), months=3)) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Orientation', + string='Orientation', selection=[('notrh', 'Notrh'), ('south', 'South'), ('east', 'East'), ('west', 'West')], - help="The garden orientation" + help="The garden orientation", + ) + active = fields.Boolean(default=True) + state = fields.Selection( + string="Status", + selection=[('new', 'New'), ('offer Received', 'Offer Received'), ('offer Accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')], + require=True, + copy=False, + default="new", ) From 842b9fb047b55ff5c23cd97998ba993a6e6a9b84 Mon Sep 17 00:00:00 2001 From: vibad Date: Wed, 22 Apr 2026 15:12:45 +0200 Subject: [PATCH 06/19] [ADD] Chapitre 6 --- estate/security/ir.model.access.csv | 2 +- estate/view/estate_property_views.xml | 82 +++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 561d4ef4824..4492e38729a 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -acces_estate_property,acces_estate_property,model_estate_property,base.group_user,1,1,1,1 +acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,1 diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index c2a0fc920ac..9d8bb8ed8d3 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -5,4 +5,86 @@ estate_property list,form + + + estate.properties.list + estate_property + + + + + + + + + + + + + + + estate.properties.form + estate_property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.properties.search + estate_property + + + + + + + + + + + + + + + + + From d3a2e678c9e0d8b6ac063dcc601d9a2c8d4615f6 Mon Sep 17 00:00:00 2001 From: "David Van Droogenbroeck (DROD)" Date: Thu, 23 Apr 2026 09:51:49 +0200 Subject: [PATCH 07/19] [FIX] estate: remove unlink permission for base users Base users were able to unlink records although they're just plebs. --- estate/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4492e38729a..811072c89ea 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,2 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,1 +acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,0 From 0eeb0b02be7ed42412cb823622b025d9f3978bcb Mon Sep 17 00:00:00 2001 From: vibad Date: Thu, 23 Apr 2026 10:46:39 +0200 Subject: [PATCH 08/19] [ADD] Chapitre 7 + lisence --- estate/__manifest__.py | 4 +++ estate/models/__init__.py | 2 +- estate/models/estate_property.py | 9 +++++-- 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 | 3 +++ estate/view/estate_action.xml | 4 +++ estate/view/estate_property_offer_views.xml | 30 +++++++++++++++++++++ estate/view/estate_property_tag_views.xml | 8 ++++++ estate/view/estate_property_type_views.xml | 8 ++++++ estate/view/estate_property_views.xml | 14 +++++++--- 12 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/view/estate_property_offer_views.xml create mode 100644 estate/view/estate_property_tag_views.xml create mode 100644 estate/view/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 39e60af8cc9..7489bf1f368 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,9 +6,13 @@ 'installable': True, 'application': True, 'author': 'vibad', + 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', + 'view/estate_property_offer_views.xml', 'view/estate_property_views.xml', + 'view/estate_property_type_views.xml', + 'view/estate_property_tag_views.xml', 'view/estate_action.xml', ], 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 5bb077f54a3..83c2dfe5477 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,7 +8,7 @@ class Estate_property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=fields.Date.add(fields.Date.today(), months=3)) + date_availability = fields.Date(copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -26,7 +26,12 @@ class Estate_property(models.Model): state = fields.Selection( string="Status", selection=[('new', 'New'), ('offer Received', 'Offer Received'), ('offer Accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')], - require=True, + required=True, copy=False, default="new", ) + property_type_id = fields.Many2one("estate_property_type", string="Property Type") + salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + tag_ids = fields.Many2many("estate_property_tag", string="Tags") + offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..331fcdfdbd3 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,14 @@ +from odoo import models, fields + + +class Estate_property_offer(models.Model): + _name = "estate_property_offer" + _description = "Offer for estate properties" + + price = fields.Float(required=True) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate_property", string="Property", required=True) + state = fields.Selection([ + ("accepted", "Accepted"), + ("refused", "Refused"), + ], string="State", copy=False) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..4bd4b9ddd54 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class Estate_property_tag(models.Model): + _name = "estate_property_tag" + _description = "tag super mega trop bien" + + 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..2f93b1bca32 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class Estate_property_type(models.Model): + _name = "estate_property_type" + _description = "APP super mega trop bien" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4492e38729a..67e0e3f720c 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 acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,1 +acces_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +acces_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +acces_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/view/estate_action.xml b/estate/view/estate_action.xml index c3dde6fb10a..cea89a5c703 100644 --- a/estate/view/estate_action.xml +++ b/estate/view/estate_action.xml @@ -4,5 +4,9 @@ + + + + diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml new file mode 100644 index 00000000000..b163f73ac45 --- /dev/null +++ b/estate/view/estate_property_offer_views.xml @@ -0,0 +1,30 @@ + + + + estate.property.offer.form + estate_property_offer + +
+ + + + + + + +
+
+
+ + + estate.property.offer.list + estate_property_offer + + + + + + + + +
diff --git a/estate/view/estate_property_tag_views.xml b/estate/view/estate_property_tag_views.xml new file mode 100644 index 00000000000..b5a6dbe7eeb --- /dev/null +++ b/estate/view/estate_property_tag_views.xml @@ -0,0 +1,8 @@ + + + + Property Tags + estate_property_tag + list,form + + diff --git a/estate/view/estate_property_type_views.xml b/estate/view/estate_property_type_views.xml new file mode 100644 index 00000000000..6411fd2663f --- /dev/null +++ b/estate/view/estate_property_type_views.xml @@ -0,0 +1,8 @@ + + + + Property Types + estate_property_type + list,form + + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 9d8bb8ed8d3..09464408da9 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -18,6 +18,7 @@ + @@ -31,6 +32,7 @@

+ @@ -52,13 +54,20 @@ + + + + + + + @@ -79,11 +88,10 @@ + - - - + From 45320c82ec8b1e3641ecbbb6b23e752356f883a3 Mon Sep 17 00:00:00 2001 From: vibad Date: Thu, 23 Apr 2026 10:49:42 +0200 Subject: [PATCH 09/19] [FIX] estate : remove all unlink permission for base users --- estate/security/ir.model.access.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 67e0e3f720c..011ca4aa1a3 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,1 -acces_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 -acces_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 -acces_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 +acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,0 +acces_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,0 +acces_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,0 +acces_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,0 From 7ab151598a8c2f1b3fcc86daf051d962dbdb07dc Mon Sep 17 00:00:00 2001 From: vibad Date: Thu, 23 Apr 2026 11:55:38 +0200 Subject: [PATCH 10/19] [CLN] remove useless blanc --- estate/models/estate_property.py | 28 +++++++++++++++++++-- estate/models/estate_property_offer.py | 21 +++++++++++++++- estate/view/estate_property_offer_views.xml | 2 ++ estate/view/estate_property_views.xml | 7 +++--- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 83c2dfe5477..3a87236ae8e 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 models, fields, api class Estate_property(models.Model): @@ -19,7 +19,7 @@ class Estate_property(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection( string='Orientation', - selection=[('notrh', 'Notrh'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="The garden orientation", ) active = fields.Boolean(default=True) @@ -35,3 +35,27 @@ class Estate_property(models.Model): buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) tag_ids = fields.Many2many("estate_property_tag", string="Tags") offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers") + total_area = fields.Integer(compute="_compute_area") + best_price = fields.Float(compute="_compute_best_price") + + @api.depends("living_area", "garden_area") + def _compute_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + if record.offer_ids: + record.best_price = max(record.offer_ids.mapped("price")) + else: + record.best_price = 0 + + @api.onchange("garden") + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 331fcdfdbd3..8be78443e17 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import models, fields, api class Estate_property_offer(models.Model): @@ -12,3 +12,22 @@ class Estate_property_offer(models.Model): ("accepted", "Accepted"), ("refused", "Refused"), ], string="State", copy=False) + validaty = fields.Integer(string="Offer Validity (days)", default=7) + date_deadline = fields.Date(string="Offer Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + @api.depends("validaty", "create_date") + def _compute_date_deadline(self): + for record in self: + if record.validaty and record.create_date: + + record.date_deadline = fields.Date.add(record.create_date, days=record.validaty) + else: + record.date_deadline = False + + def _inverse_date_deadline(self): + for record in self: + if record.date_deadline and record.create_date: + create_date = fields.Date.to_date(record.create_date) + record.validaty = (record.date_deadline - create_date).days + else: + record.validaty = 0 diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml index b163f73ac45..1ea5417aa2c 100644 --- a/estate/view/estate_property_offer_views.xml +++ b/estate/view/estate_property_offer_views.xml @@ -10,6 +10,8 @@ + + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 09464408da9..f7a0005ea59 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -37,10 +37,12 @@ + - + + @@ -56,6 +58,7 @@ + @@ -66,8 +69,6 @@ - - From c4f10323ad5b6f50b1e00f758b9e6618280d8be2 Mon Sep 17 00:00:00 2001 From: vibad Date: Thu, 23 Apr 2026 16:15:12 +0200 Subject: [PATCH 11/19] [ADD] Chapitre 10 --- estate/models/estate_property.py | 44 +++++++++++++++++++++ estate/models/estate_property_offer.py | 27 ++++++++++++- estate/models/estate_property_tag.py | 5 +++ estate/models/estate_property_type.py | 5 +++ estate/security/ir.model.access.csv | 2 +- estate/view/estate_property_offer_views.xml | 2 + estate/view/estate_property_views.xml | 7 +++- 7 files changed, 89 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3a87236ae8e..1ffd2110916 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import models, fields, api +from odoo.exceptions import UserError, ValidationError class Estate_property(models.Model): @@ -29,6 +30,8 @@ class Estate_property(models.Model): required=True, copy=False, default="new", + compute="_compute_state", + store=True, ) property_type_id = fields.Many2one("estate_property_type", string="Property Type") salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) @@ -38,6 +41,16 @@ class Estate_property(models.Model): total_area = fields.Integer(compute="_compute_area") best_price = fields.Float(compute="_compute_best_price") + _check_expected_price = models.Constraint( + "CHECK(expected_price > 0)", + message="The expected price must be strictly positive", + ) + + _check_selling_price = models.Constraint( + "CHECK(selling_price >= 0)", + message="The selling price cannot be negative", + ) + @api.depends("living_area", "garden_area") def _compute_area(self): for record in self: @@ -59,3 +72,34 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + @api.depends("offer_ids") + def _compute_state(self): + if self.offer_ids: + self.state = "offer Received" + for offer in self.offer_ids: + if offer.state == "accepted": + self.state = "offer Accepted" + break + else: + self.state = "new" + + def action_sold(self): + for record in self: + if record.state != "cancelled" and record.state != "sold": + record.state = "sold" + else: + raise UserError("A property that is cancelled or already sold cannot be sold.") + + def action_cancel(self): + for record in self: + if record.state != "sold" and record.state != "cancelled": + record.state = "cancelled" + else: + raise UserError("A property that is sold or already cancelled cannot be cancelled.") + + @api.constrains("selling_price", "expected_price") + def _check_enough_selling_price(self): + for record in self: + if record.selling_price and record.selling_price < record.expected_price * 0.9: + raise ValidationError("The selling price cannot be less than 90% of the expected price.") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8be78443e17..ada5cae6eff 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,5 @@ from odoo import models, fields, api - +from odoo.exceptions import UserError class Estate_property_offer(models.Model): _name = "estate_property_offer" @@ -15,6 +15,11 @@ class Estate_property_offer(models.Model): validaty = fields.Integer(string="Offer Validity (days)", default=7) date_deadline = fields.Date(string="Offer Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline") + _check_price = models.Constraint( + "CHECK(price > 0)", + message="The price must be strictly positive", + ) + @api.depends("validaty", "create_date") def _compute_date_deadline(self): for record in self: @@ -31,3 +36,23 @@ def _inverse_date_deadline(self): record.validaty = (record.date_deadline - create_date).days else: record.validaty = 0 + + def accept_offer(self): + for record in self: + if record.state == "accepted" or record.state == "refused": + raise UserError("This offer has already been accepted or refused.") + for offer in record.property_id.offer_ids: + if offer.state == "accepted": + raise UserError("Another offer has already been accepted for this property.") + record.state = "accepted" + record.property_id.state = "offer Accepted" + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + return True + + def refuse_offer(self): + for record in self: + if record.state == "accepted" or record.state == "refused": + raise UserError("This offer has already been accepted or refused.") + record.state = "refused" + return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 4bd4b9ddd54..1767c0b04fb 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,3 +6,8 @@ class Estate_property_tag(models.Model): _description = "tag super mega trop bien" name = fields.Char(required=True) + + _check_name = models.Constraint( + "UNIQUE(name)", + message="The name of the tag must be unique", + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 2f93b1bca32..3b35b1eb5ef 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,3 +6,8 @@ class Estate_property_type(models.Model): _description = "APP super mega trop bien" name = fields.Char(required=True) + + _check_name = models.Constraint( + "UNIQUE(name)", + message="The name of the property type must be unique", + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 011ca4aa1a3..c6876dae518 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,4 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,0 acces_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,0 acces_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,0 -acces_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,0 +acces_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml index 1ea5417aa2c..d70ec2aca77 100644 --- a/estate/view/estate_property_offer_views.xml +++ b/estate/view/estate_property_offer_views.xml @@ -25,6 +25,8 @@ + + +

+ +

+ + + + + + + + + + + + + +
+ + + + estate.property.type.list + estate_property_type + + + + + + + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 19470d5cb89..3c5e8fc7319 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -4,20 +4,22 @@ Properties estate_property list,form + {'search_default_available': True} - +s estate.properties.list estate_property - + + - + @@ -29,20 +31,21 @@
-

- + - + - + @@ -59,8 +62,8 @@ - - + + @@ -68,7 +71,7 @@ - + @@ -90,13 +93,13 @@ - + - + From bce8978edcd939b39e1afff0a98141649b21e655 Mon Sep 17 00:00:00 2001 From: vibad Date: Mon, 27 Apr 2026 11:46:00 +0200 Subject: [PATCH 14/19] [ADD] Chapiter 12 --- estate/__manifest__.py | 1 + estate/models/__init__.py | 2 +- estate/models/estate_property.py | 7 ++++++- estate/models/estate_property_offer.py | 10 ++++++++++ estate/models/inherited_model.py | 7 +++++++ estate/security/ir.model.access.csv | 2 +- estate/view/estate_inherit_view.xml | 15 +++++++++++++++ 7 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 estate/models/inherited_model.py create mode 100644 estate/view/estate_inherit_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7489bf1f368..55a5776294f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,6 +13,7 @@ 'view/estate_property_views.xml', 'view/estate_property_type_views.xml', 'view/estate_property_tag_views.xml', + 'view/estate_inherit_view.xml', 'view/estate_action.xml', ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 93a6bd86abd..5760868dd17 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, inherited_model diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ba48178f6fe..de2f8100e7b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -79,7 +79,6 @@ def _compute_state(self): if self.state in ["sold", "cancelled"]: return if self.offer_ids: - self.state = "offer Received" for offer in self.offer_ids: if offer.state == "accepted": self.state = "offer Accepted" @@ -107,3 +106,9 @@ def _check_enough_selling_price(self): for record in self: if record.selling_price and record.selling_price < record.expected_price * 0.9: raise ValidationError("The selling price cannot be less than 90% of the expected price.") + + @api.ondelete(at_uninstall=False) + def _ondelete_cancel_new(self): + for record in self: + if record.state not in ["new", "cancelled"]: + raise UserError("You can only delete offers that are new or cancelled.") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 326089cdd2c..b3bcc71fb7f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -61,3 +61,13 @@ def refuse_offer(self): raise UserError("This offer has already been accepted or refused.") record.state = "refused" return True + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if self.env["estate_property"].browse(vals["property_id"]).state == "new": + self.env["estate_property"].browse(vals["property_id"]).state = "offer Received" + max_price = max(self.env["estate_property_offer"].search([("property_id", "=", vals["property_id"])]).mapped("price") or [0]) + if vals["price"] <= max_price: + raise UserError("The price must be higher than the current highest offer.") # Error for one offer blocks all offers in the list + return super().create(vals_list) diff --git a/estate/models/inherited_model.py b/estate/models/inherited_model.py new file mode 100644 index 00000000000..f403d0f1c57 --- /dev/null +++ b/estate/models/inherited_model.py @@ -0,0 +1,7 @@ +from odoo import models, fields + + +class Estate_inherited_model(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many("estate_property", "salesperson_id", string="Properties") diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index c6876dae518..ac76fe94279 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,0 +acces_estate_property,estate_property,model_estate_property,base.group_user,1,1,1,1 acces_estate_property_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,0 acces_estate_property_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,0 acces_estate_property_offer,estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/view/estate_inherit_view.xml b/estate/view/estate_inherit_view.xml new file mode 100644 index 00000000000..0c0b5705924 --- /dev/null +++ b/estate/view/estate_inherit_view.xml @@ -0,0 +1,15 @@ + + + + estate.inherit.view + res.users + + + + + + + + + + From bf2cafe2adc0e310a4ac4663e2ac7df001dbbcd5 Mon Sep 17 00:00:00 2001 From: vibad Date: Mon, 27 Apr 2026 13:23:22 +0200 Subject: [PATCH 15/19] [CLN] remove warning and blank --- estate/models/estate_property_offer.py | 2 +- estate/models/inherited_model.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b3bcc71fb7f..9e0b23f023a 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -61,7 +61,7 @@ def refuse_offer(self): raise UserError("This offer has already been accepted or refused.") record.state = "refused" return True - + @api.model_create_multi def create(self, vals_list): for vals in vals_list: diff --git a/estate/models/inherited_model.py b/estate/models/inherited_model.py index f403d0f1c57..2fdc0796823 100644 --- a/estate/models/inherited_model.py +++ b/estate/models/inherited_model.py @@ -4,4 +4,4 @@ class Estate_inherited_model(models.Model): _inherit = "res.users" - property_ids = fields.One2many("estate_property", "salesperson_id", string="Properties") + property_ids = fields.One2many("estate_property", "salesperson_id", string=" Estate properties") From 867cbfe8455c073c62176fe99c6260d03863b9aa Mon Sep 17 00:00:00 2001 From: vibad Date: Mon, 27 Apr 2026 16:31:01 +0200 Subject: [PATCH 16/19] [ADD] Chapiter 13 + PR fix --- estate/__manifest__.py | 1 + estate/models/__init__.py | 6 ++- estate/models/estate_property.py | 46 +++++++++++---------- estate/models/estate_property_offer.py | 17 ++++---- estate/models/estate_property_tag.py | 4 +- estate/models/estate_property_type.py | 17 ++++---- estate/models/inherited_model.py | 6 +-- estate/view/estate_property_offer_views.xml | 6 +-- estate/view/estate_property_tag_views.xml | 4 +- estate/view/estate_property_type_views.xml | 6 +-- estate/view/estate_property_views.xml | 8 ++-- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 14 +++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 26 ++++++++++++ 15 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 55a5776294f..c6277d7faa9 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,7 @@ 'application': True, 'author': 'vibad', 'license': 'LGPL-3', + 'version': '1.0', 'data': [ 'security/ir.model.access.csv', 'view/estate_property_offer_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5760868dd17..c0917a3d550 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,5 @@ -from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, inherited_model +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer +from . import inherited_model diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index de2f8100e7b..0fce99a860b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,9 @@ -from odoo import models, fields, api +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError class Estate_property(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "APP super mega trop bien" _order = "id desc" @@ -20,25 +20,25 @@ class Estate_property(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Orientation', - selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + string="Orientation", + selection=[("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")], help="The garden orientation", ) active = fields.Boolean(default=True) state = fields.Selection( string="Status", - selection=[('new', 'New'), ('offer Received', 'Offer Received'), ('offer Accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')], + selection=[("new", "New"), ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")], required=True, copy=False, default="new", compute="_compute_state", store=True, ) - property_type_id = fields.Many2one("estate_property_type", string="Property Type") + property_type_id = fields.Many2one("estate.property.type", string="Property Type") salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) - tag_ids = fields.Many2many("estate_property_tag", string="Tags") - offer_ids = fields.One2many("estate_property_offer", "property_id", string="Offers") + tag_ids = fields.Many2many("estate.property.tag", string="Tags") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") total_area = fields.Integer(compute="_compute_area") best_price = fields.Float(compute="_compute_best_price") @@ -67,25 +67,27 @@ def _compute_best_price(self): @api.onchange("garden") def _onchange_garden(self): - if self.garden: - self.garden_area = 10 - self.garden_orientation = "north" - else: - self.garden_area = 0 - self.garden_orientation = False + for record in self: + if record.garden: + record.garden_area = 10 + record.garden_orientation = "north" + else: + record.garden_area = 0 + record.garden_orientation = False @api.depends("offer_ids", "offer_ids.state") def _compute_state(self): - if self.state in ["sold", "cancelled"]: - return - if self.offer_ids: - for offer in self.offer_ids: - if offer.state == "accepted": - self.state = "offer Accepted" + for record in self: + if record.state in ["sold", "cancelled"]: + return + if record.offer_ids: + for offer in record.offer_ids: + if offer.state == "accepted": + record.state = "offer_accepted" break else: - self.state = "new" - self.selling_price = 0 + record.state = "new" + record.selling_price = 0 def action_sold(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9e0b23f023a..031c4d4228f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,17 +1,17 @@ -from odoo import models, fields, api +from odoo import api, fields, models from odoo.exceptions import UserError class Estate_property_offer(models.Model): - _name = "estate_property_offer" + _name = "estate.property.offer" _description = "Offer for estate properties" _order = "price desc" price = fields.Float(required=True) 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) state = fields.Selection([ - ('new', 'New'), + ("new", "New"), ("accepted", "Accepted"), ("refused", "Refused"), ], default="new", string="State", copy=False) @@ -64,10 +64,13 @@ def refuse_offer(self): @api.model_create_multi def create(self, vals_list): + max_price_list = 0 for vals in vals_list: - if self.env["estate_property"].browse(vals["property_id"]).state == "new": - self.env["estate_property"].browse(vals["property_id"]).state = "offer Received" - max_price = max(self.env["estate_property_offer"].search([("property_id", "=", vals["property_id"])]).mapped("price") or [0]) + if self.env["estate.property"].browse(vals["property_id"]).state == "new": + self.env["estate.property"].browse(vals["property_id"]).state = "offer_received" + max_price = max(self.env["estate.property.offer"].search([("property_id", "=", vals["property_id"])]).mapped("price") or [0]) + max_price_list = max(max_price_list, max_price) if vals["price"] <= max_price: raise UserError("The price must be higher than the current highest offer.") # Error for one offer blocks all offers in the list + max_price_list = max(max_price_list, vals["price"]) return super().create(vals_list) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index f77039bdd56..a1f44dc663d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,8 +1,8 @@ -from odoo import models, fields +from odoo import fields, models class Estate_property_tag(models.Model): - _name = "estate_property_tag" + _name = "estate.property.tag" _description = "tag super mega trop bien" _order = "name" diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index c80d754a3b2..94c555e6fd6 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,21 +1,20 @@ -from odoo import models, fields +from odoo import fields, models class Estate_property_type(models.Model): - _name = "estate_property_type" + _name = "estate.property.type" _description = "APP super mega trop bien" _order = "sequence, name" - - name = fields.Char(required=True) - sequence = fields.Integer() - offer_ids = fields.One2many("estate_property_offer", "property_type_id", string="Offers") - offer_count = fields.Integer(compute="_compute_offer_count") - _check_name = models.Constraint( "UNIQUE(name)", message="The name of the property type must be unique", ) - property_ids = fields.One2many("estate_property", "property_type_id", string="Properties") + + name = fields.Char(required=True) + sequence = fields.Integer() + offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") + offer_count = fields.Integer(compute="_compute_offer_count") + property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") def _compute_offer_count(self): for record in self: diff --git a/estate/models/inherited_model.py b/estate/models/inherited_model.py index 2fdc0796823..01da82ddf7c 100644 --- a/estate/models/inherited_model.py +++ b/estate/models/inherited_model.py @@ -1,7 +1,7 @@ -from odoo import models, fields +from odoo import fields, models -class Estate_inherited_model(models.Model): +class Estate_users_model(models.Model): _inherit = "res.users" - property_ids = fields.One2many("estate_property", "salesperson_id", string=" Estate properties") + property_ids = fields.One2many("estate.property", "salesperson_id", string=" Estate properties") diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml index 1dc5a6a6e7d..31affd5828e 100644 --- a/estate/view/estate_property_offer_views.xml +++ b/estate/view/estate_property_offer_views.xml @@ -2,14 +2,14 @@ Offers - estate_property_offer + estate.property.offer list,form [('property_type_id', '=', active_id)] estate.property.offer.form - estate_property_offer + estate.property.offer @@ -27,7 +27,7 @@ estate.property.offer.list - estate_property_offer + estate.property.offer diff --git a/estate/view/estate_property_tag_views.xml b/estate/view/estate_property_tag_views.xml index 86ec99c9694..6c63e2ced08 100644 --- a/estate/view/estate_property_tag_views.xml +++ b/estate/view/estate_property_tag_views.xml @@ -2,13 +2,13 @@ Property Tags - estate_property_tag + estate.property.tag list,form estate.property.tag.list - estate_property_tag + estate.property.tag diff --git a/estate/view/estate_property_type_views.xml b/estate/view/estate_property_type_views.xml index 7de1c5d1c14..3507bd13318 100644 --- a/estate/view/estate_property_type_views.xml +++ b/estate/view/estate_property_type_views.xml @@ -2,13 +2,13 @@ Property Types - estate_property_type + estate.property.type list,form estate.property.type.form - estate_property_type + estate.property.type @@ -38,7 +38,7 @@ estate.property.type.list - estate_property_type + estate.property.type diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 3c5e8fc7319..e825bfa7b9e 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -2,14 +2,14 @@ Properties - estate_property + estate.property list,form {'search_default_available': True} s estate.properties.list - estate_property + estate.property @@ -27,7 +27,7 @@ s estate.properties.form - estate_property + estate.property
@@ -87,7 +87,7 @@ s estate.properties.search - estate_property + estate.property 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..ceb8a89fc2f --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,14 @@ +{ + 'name': 'estate_account', + 'depends': [ + 'base', + 'estate', + 'account', + ], + 'installable': True, + 'application': True, + 'author': 'vibad', + 'license': 'LGPL-3', + 'version': '1.0', + +} 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..a8f96236368 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,26 @@ +from odoo import Command, models + + +class Estate_account_model(models.Model): + _inherit = "estate.property" + + def action_sold(self): + print("Overriding the action_sold method in the inherited model") + invoice = self.env["account.move"].create({ + "move_type": "out_invoice", + "partner_id": self.buyer_id.id, + "invoice_line_ids": [ + Command.create({ + "name": self.name + " - Commission", + "quantity": 1, + "price_unit": self.selling_price * 0.06, # 6% of the selling price as commission + }), + Command.create({ + "name": self.name + " - Admin fee", + "quantity": 1, + "price_unit": 100, + }), + ], + }) + invoice.action_post() + return super().action_sold() From 9061548d09a59a19025c3439492cc598211d9ce2 Mon Sep 17 00:00:00 2001 From: vibad Date: Mon, 27 Apr 2026 17:20:53 +0200 Subject: [PATCH 17/19] [CLN] clean blank, print and indentation --- estate/models/estate_property.py | 6 +++--- estate_account/__manifest__.py | 1 - estate_account/models/estate_property.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0fce99a860b..fb341b8fd97 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -85,9 +85,9 @@ def _compute_state(self): if offer.state == "accepted": record.state = "offer_accepted" break - else: - record.state = "new" - record.selling_price = 0 + else: + record.state = "new" + record.selling_price = 0 def action_sold(self): for record in self: diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index ceb8a89fc2f..dbcc7393541 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -10,5 +10,4 @@ 'author': 'vibad', 'license': 'LGPL-3', 'version': '1.0', - } diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index a8f96236368..f150b6396f5 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -5,7 +5,6 @@ class Estate_account_model(models.Model): _inherit = "estate.property" def action_sold(self): - print("Overriding the action_sold method in the inherited model") invoice = self.env["account.move"].create({ "move_type": "out_invoice", "partner_id": self.buyer_id.id, From 8d99e2360ab0cc8576402c8fdd308f4cfc3f71df Mon Sep 17 00:00:00 2001 From: "David Van Droogenbroeck (DROD)" Date: Tue, 28 Apr 2026 15:11:43 +0200 Subject: [PATCH 18/19] [IMP] estate: add basic test cases This commit is here to introduce the testing framework of Odoo. Try running the tests using `--test-tags TestEstateProperty`. Doc: https://www.odoo.com/documentation/18.0/developer/reference/backend/testing.html?highlight=tests#invocation The tests were made such that the first one should work but the second one should fail. Your job is to ensure both tests pass in the end. You should update the behaviour of the appropriate models. If you want, you can also add a small test of your own to get a feel for it. --- estate/tests/__init__.py | 1 + estate/tests/test_estate_property.py | 42 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 estate/tests/__init__.py create mode 100644 estate/tests/test_estate_property.py diff --git a/estate/tests/__init__.py b/estate/tests/__init__.py new file mode 100644 index 00000000000..18f3a50c3e1 --- /dev/null +++ b/estate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_estate_property \ No newline at end of file diff --git a/estate/tests/test_estate_property.py b/estate/tests/test_estate_property.py new file mode 100644 index 00000000000..7202f8b67a0 --- /dev/null +++ b/estate/tests/test_estate_property.py @@ -0,0 +1,42 @@ +from odoo.exceptions import ValidationError +from odoo.tests import TransactionCase +from odoo import Command + + +class TestEstateProperty(TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.estate = cls.env['estate.property'].create({ + 'name': 'Super test estate', + 'expected_price': 100000.0, + 'state': 'new', + }) + cls.test_partner = cls.env['res.partner'].create({ + 'name': 'Maman ours', + }) + + def test_estate_best_price(self): + ''' + Ensure best price is correctly updated when an offer is received. + ''' + self.assertEqual(self.estate.best_price, 0.0) + self.estate.offer_ids = [Command.create({ + 'price': 125000.0, + 'partner_id': self.test_partner.id, + })] + self.assertEqual(self.estate.best_price, 125000.0) + + def test_accept_offer_south_facing_garden(self): + ''' + Ensure offers for estates with south-facing gardens can only be accepted if above expected + price. + ''' + self.estate.expected_price = 500000 + self.estate.offer_ids = [Command.create({ + 'price': 475000.0, + 'partner_id': self.test_partner.id, + })] + with self.assertRaises(ValidationError): + self.estate.offer_ids.accept_offer() From 5dd56aff6ecf039da572c0d68531ca215c507d97 Mon Sep 17 00:00:00 2001 From: vibad Date: Tue, 28 Apr 2026 16:33:02 +0200 Subject: [PATCH 19/19] [ADD] chapiter 14 + resolve test --- estate/models/estate_property.py | 1 - estate/models/estate_property_offer.py | 2 ++ estate/view/estate_property_views.xml | 35 ++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index fb341b8fd97..98cb8c1302b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -87,7 +87,6 @@ def _compute_state(self): break else: record.state = "new" - record.selling_price = 0 def action_sold(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 031c4d4228f..e156550797b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -50,6 +50,8 @@ def accept_offer(self): for offer in record.property_id.offer_ids: if offer.state == "accepted": raise UserError("Another offer has already been accepted for this property.") + if record.property_id.garden_orientation == "south" and record.price <= record.property_id.expected_price: + raise UserError("The price must be more than the expected price for properties with a south-facing garden.") record.state = "accepted" record.property_id.selling_price = record.price record.property_id.buyer_id = record.partner_id diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index e825bfa7b9e..12e4afa65ef 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -3,7 +3,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_available': True} s @@ -11,7 +11,7 @@ s estate.properties.list estate.property - + @@ -33,7 +33,7 @@ s

@@ -71,7 +71,7 @@ s - + @@ -99,9 +99,34 @@ s - + + + estate.properties.kanban + estate.property + + + + + + + +
+ +
+
+ Expected price: +
+
+ Best Offer: +
+ +
+
+
+
+