From d2c3b721fe09c546cee3275c4853d0f321dee8fb Mon Sep 17 00:00:00 2001 From: amsli Date: Tue, 19 May 2026 16:45:00 +0200 Subject: [PATCH 01/20] [ADD] estate: chapter1 to 5 --- estate/.__manifest__.py.swp | Bin 0 -> 1024 bytes estate/__init__.py | 1 + estate/__manifest__.py | 10 +++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 28 +++++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 9 ++++++++ estate/views/estate_property_views.xml | 10 +++++++++ 8 files changed, 61 insertions(+) create mode 100644 estate/.__manifest__.py.swp create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/.__manifest__.py.swp b/estate/.__manifest__.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..4cd9f66df8782b5f57265265d70eb5f34f818f9c GIT binary patch literal 1024 zcmYc?$V<%2S1{KzVn6{R!K@7VDf#&*f;c#dxy3n|x;cpjCHVz7lz@ff<8u@9GSgCv XOXB183M%m_9hDmmfzc44LkIu>@Oux? literal 0 HcmV?d00001 diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..e42249af364 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': 'Real Estate', + 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + ], + 'application': True, +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..cbb75ffb47a --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,28 @@ +from odoo import models, fields + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(string="Title", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + date_availability = fields.Date(string="Available From") + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bedrooms") + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area") + + garden_orientation = fields.Selection( + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ], + string="Garden Orientation" + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..d9d6ba57cc5 --- /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 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..d8665890254 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..51f4faf394a --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,10 @@ + + + + + Properties + estate.property + list,form + + + From 7d75d285d604f389fcca408ba5f1f45bcd0fedc4 Mon Sep 17 00:00:00 2001 From: amsli Date: Wed, 20 May 2026 11:04:39 +0200 Subject: [PATCH 02/20] [ADD] estate :Add active and state fields with dynamic defaults to estate model --- estate/models/estate_property.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index cbb75ffb47a..bbf71ec641e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,6 @@ from odoo import models, fields +from datetime import date +from dateutil.relativedelta import relativedelta class EstateProperty(models.Model): _name = "estate.property" @@ -7,10 +9,12 @@ class EstateProperty(models.Model): name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Available From") + date_availability = fields.Date(string="Available From", + copy=False, + default=lambda self: date.today() + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price") - bedrooms = fields.Integer(string="Bedrooms") + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer(string="Bedrooms", default=2) living_area = fields.Integer(string="Living Area") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") @@ -26,3 +30,19 @@ class EstateProperty(models.Model): ], string="Garden Orientation" ) + + active = fields.Boolean(string="Active", default=True) + + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + string="Status", + required=True, + copy=False, + default='new' + ) From 8852780e92df29ea10cc308924a85d40be6c17b6 Mon Sep 17 00:00:00 2001 From: amsli Date: Wed, 20 May 2026 11:12:16 +0200 Subject: [PATCH 03/20] [ADD] estate: Create custom list, form, and search views for estate property --- estate/views/estate_property_views.xml | 85 +++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 51f4faf394a..12edbaff234 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,10 +1,91 @@ - + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + Properties estate.property list,form +
-
+ \ No newline at end of file From d42f161851dfd077eff692cfbcd095fe1554d7ef Mon Sep 17 00:00:00 2001 From: amsli Date: Wed, 20 May 2026 14:40:23 +0200 Subject: [PATCH 04/20] [ADD] estate: chapter 6 --- estate/.__manifest__.py.swp | Bin 1024 -> 0 bytes estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 6 +++ estate/models/estate_property_offer.py | 19 ++++++++ estate/models/estate_property_tag.py | 7 +++ estate/models/estate_property_type.py | 7 +++ estate/security/ir.model.access.csv | 5 +- estate/views/estate_menus.xml | 9 ++-- estate/views/estate_property_views.xml | 63 +++++++++++++++++++++---- 9 files changed, 107 insertions(+), 12 deletions(-) delete mode 100644 estate/.__manifest__.py.swp 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 diff --git a/estate/.__manifest__.py.swp b/estate/.__manifest__.py.swp deleted file mode 100644 index 4cd9f66df8782b5f57265265d70eb5f34f818f9c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmYc?$V<%2S1{KzVn6{R!K@7VDf#&*f;c#dxy3n|x;cpjCHVz7lz@ff<8u@9GSgCv XOXB183M%m_9hDmmfzc44LkIu>@Oux? 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 bbf71ec641e..83a8c3a1175 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -46,3 +46,9 @@ class EstateProperty(models.Model): copy=False, default='new' ) + + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) + 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..cfbdbc6a52c --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,19 @@ +from odoo import models, fields + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + price = fields.Float(string="Price") + + status = fields.Selection( + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + string="Status", + copy=False + ) + + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True) \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..4bed246e5df --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real Estate Property Tag" + + name = fields.Char(string="Name", required=True) \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..d0fdfacbb9e --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real Estate Property Type" + + name = fields.Char(string="Name", required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d9d6ba57cc5..05bd9eefba4 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 +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 +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 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index d8665890254..3d71f9e102e 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,8 +2,11 @@ - + + - + + + - + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 12edbaff234..f89dc86d300 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,12 +2,40 @@ + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + +
+
+
+ estate.property.list estate.property + + @@ -28,9 +56,11 @@

+ + @@ -52,6 +82,12 @@ + + + + + + @@ -64,18 +100,17 @@ + - - - - - + + + + + @@ -87,5 +122,17 @@ list,form
+ + Property Types + estate.property.type + list,form + + + + Property Tags + estate.property.tag + list,form + +
-
\ No newline at end of file + From b6e9487b7ff5afc271e436cdb1e17cde0e653527 Mon Sep 17 00:00:00 2001 From: amsli Date: Wed, 20 May 2026 14:50:03 +0200 Subject: [PATCH 05/20] [ADD] estate: chapter 7 --- estate/__manifest__.py | 3 ++ estate/views/estate_menus.xml | 10 ++-- estate/views/estate_property_offer_views.xml | 34 +++++++++++++ estate/views/estate_property_tag_views.xml | 36 ++++++++++++++ estate/views/estate_property_type_views.xml | 36 ++++++++++++++ estate/views/estate_property_views.xml | 50 +++----------------- 6 files changed, 121 insertions(+), 48 deletions(-) create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e42249af364..bad661c750a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,6 +3,9 @@ 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_offer_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_type_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', ], diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 3d71f9e102e..83b351312dd 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,11 +2,11 @@ - - + + - - - + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..7dde992b607 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,34 @@ + + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+ +
+
\ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..04e28ad68fa --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,36 @@ + + + + + + estate.property.tag.list + estate.property.tag + + + + + + + + + estate.property.tag.form + estate.property.tag + +
+ + + + + +
+
+
+ + + Property Tags + estate.property.tag + list,form + + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..20d6b5eebd9 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,36 @@ + + + + + + estate.property.type.list + estate.property.type + + + + + + + + + estate.property.type.form + estate.property.type + +
+ + + + + +
+
+
+ + + Property Types + estate.property.type + list,form + + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f89dc86d300..bcc3cf74edd 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,32 +2,6 @@ - - estate.property.offer.list - estate.property.offer - - - - - - - - - - - estate.property.offer.form - estate.property.offer - -
- - - - - -
-
-
- estate.property.list estate.property @@ -82,6 +56,9 @@ + + + @@ -94,8 +71,8 @@ - - estate.property.search + + estate.property.search.v2 estate.property @@ -106,9 +83,7 @@ - - @@ -120,19 +95,8 @@ Properties estate.property list,form - - - - Property Types - estate.property.type - list,form - - - - Property Tags - estate.property.tag - list,form +
-
+ \ No newline at end of file From 914ad099ebcf089f27aed600f829ef952e58c28c Mon Sep 17 00:00:00 2001 From: amsli Date: Wed, 20 May 2026 15:54:56 +0200 Subject: [PATCH 06/20] [ADD] estate: chapter 7 fixes --- estate/views/estate_menus.xml | 14 +++++++-- estate/views/estate_property_offer_views.xml | 22 ++------------ estate/views/estate_property_tag_views.xml | 32 +++++--------------- estate/views/estate_property_type_views.xml | 32 +++++--------------- estate/views/estate_property_views.xml | 22 ++++++-------- 5 files changed, 39 insertions(+), 83 deletions(-) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 83b351312dd..490407d2314 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,12 +1,22 @@ - + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 7dde992b607..bc943c1e88a 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,34 +1,16 @@ - + - estate.property.offer.list estate.property.offer - + - - - estate.property.offer.form - estate.property.offer - -
- - - - - - - -
-
-
-
\ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 04e28ad68fa..7b7d8f0eed6 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -1,36 +1,20 @@ - + + + Property Tags + estate.property.tag + list,form + estate.property.tag.list estate.property.tag - - + + - - - estate.property.tag.form - estate.property.tag - -
- - - - - -
-
-
- - - Property Tags - estate.property.tag - list,form - -
\ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 20d6b5eebd9..90fbb747146 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -1,36 +1,20 @@ - + + + Property Types + estate.property.type + list,form + estate.property.type.list estate.property.type - - + + - - - estate.property.type.form - estate.property.type - -
- - - - - -
-
-
- - - Property Types - estate.property.type - list,form - -
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index bcc3cf74edd..a9c6ed6fc7f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,4 +1,4 @@ - + @@ -71,22 +71,18 @@ - - estate.property.search.v2 + + estate.property.search estate.property - - - - - + + - - - - + + + @@ -95,7 +91,7 @@ Properties estate.property list,form - + From 272a5b9618f76666cb7812e38c26e649052db45e Mon Sep 17 00:00:00 2001 From: amsli Date: Wed, 20 May 2026 16:30:02 +0200 Subject: [PATCH 07/20] [ADD] estate: chapter 8 implement property offers, computed areas, and garden onchanges --- estate/models/estate_property.py | 25 ++++++++++++++++++++++++- estate/models/estate_property_offer.py | 22 ++++++++++++++++++++-- estate/views/estate_property_views.xml | 4 +++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 83a8c3a1175..7215d4c271c 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 from datetime import date from dateutil.relativedelta import relativedelta @@ -52,3 +52,26 @@ class EstateProperty(models.Model): salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) tag_ids = fields.Many2many("estate.property.tag", string="Tags") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + + total_area = fields.Integer(string="Total Area (sqm)", compute="_compute_total_area") + best_price = fields.Float(string="Best Offer", compute="_compute_best_price") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends("offer_ids.price") + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped("price") + record.best_price = max(prices) if prices else 0.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 \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index cfbdbc6a52c..c0e1890afdd 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 models, fields, api +from datetime import date, timedelta class EstatePropertyOffer(models.Model): _name = "estate.property.offer" @@ -16,4 +17,21 @@ class EstatePropertyOffer(models.Model): ) partner_id = fields.Many2one("res.partner", string="Partner", required=True) - property_id = fields.Many2one("estate.property", string="Property", required=True) \ No newline at end of file + property_id = fields.Many2one("estate.property", string="Property", required=True) + + validity = fields.Integer(string="Validity (Days)", default=7) + date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + @api.depends("create_date", "validity") + def _compute_date_deadline(self): + for record in self: + base_date = record.create_date.date() if record.create_date else date.today() + record.date_deadline = base_date + timedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + base_date = record.create_date.date() if record.create_date else date.today() + if record.date_deadline: + record.validity = (record.date_deadline - base_date).days + else: + record.validity = 7 \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a9c6ed6fc7f..e1278553658 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -40,6 +40,7 @@ + @@ -53,6 +54,7 @@ + @@ -82,7 +84,7 @@ - + From b0e70e013a8a17dee90bc4771c3de0554dbbc1f4 Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 09:50:37 +0200 Subject: [PATCH 08/20] [ADD] estate: Chapter 9 Action Buttons --- estate/models/estate_property.py | 20 +++++++++++- estate/models/estate_property_offer.py | 23 +++++++++++++- estate/views/estate_property_offer_views.xml | 32 ++++++++++++++++++-- estate/views/estate_property_views.xml | 6 ++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 7215d4c271c..581b6408fae 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 from datetime import date from dateutil.relativedelta import relativedelta @@ -47,6 +48,8 @@ class EstateProperty(models.Model): default='new' ) + + property_type_id = fields.Many2one("estate.property.type", string="Property Type") buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) @@ -74,4 +77,19 @@ def _onchange_garden(self): self.garden_orientation = "north" else: self.garden_area = 0 - self.garden_orientation = False \ No newline at end of file + self.garden_orientation = False + + + def action_cancel_property(self): + for property in self: + if property.state == "sold": + raise UserError(_("Sold properties cannot be cancelled.")) + property.state = "cancelled" + return True + + def action_sold_property(self): + for property in self: + if property.state == "cancelled": + raise UserError(_("Cancelled properties cannot be sold.")) + property.state = "sold" + return True \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index c0e1890afdd..5aa7fcc9d9a 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ from odoo import models, fields, api +from odoo.exceptions import UserError from datetime import date, timedelta class EstatePropertyOffer(models.Model): @@ -34,4 +35,24 @@ def _inverse_date_deadline(self): if record.date_deadline: record.validity = (record.date_deadline - base_date).days else: - record.validity = 7 \ No newline at end of file + record.validity = 7 + + def action_accept_offer(self): + for offer in self: + # Check if any sister lines under the same property parent are already accepted + if any(status == "accepted" for status in offer.property_id.offer_ids.mapped("status")): + raise UserError(_("An offer has already been accepted for this property.")) + + offer.status = "accepted" + offer.property_id.buyer_id = offer.partner_id + offer.property_id.selling_price = offer.price + offer.property_id.state = "offer_accepted" + return True + + def action_reject_offer(self): + for offer in self: + offer.status = "refused" + return True + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index bc943c1e88a..8863ac713d2 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,16 +1,42 @@ + estate.property.offer.list estate.property.offer - - - + + + + + + + + +
+

+ +

+
+ + + + + + + + + + + +
+
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8ce64c220d1..4b2be0acf3a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,16 +6,18 @@ estate.property.list estate.property - + - + - + @@ -36,11 +38,11 @@

- + - + @@ -61,11 +63,11 @@ - + - + @@ -86,9 +88,11 @@ - + + + - + @@ -100,6 +104,7 @@ estate.property list,form + {'search_default_available': True} From 31b4322a0e688ccb651aee6bc0a375e7c0140033 Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 16:41:53 +0200 Subject: [PATCH 12/20] [ADD] estate: Python Inheritance --- estate/models/estate_property.py | 15 +++++++++++---- estate/models/estate_property_offer.py | 20 ++++++++++++++++++-- estate/models/estate_property_tag.py | 2 +- estate/models/estate_property_type.py | 4 +++- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2db20836a18..af38b7d2704 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -82,21 +82,22 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + def action_cancel_property(self): for property in self: if property.state == "sold": - raise UserError(("Sold properties cannot be cancelled.")) + raise UserError("Sold properties cannot be cancelled.") property.state = "cancelled" return True def action_sold_property(self): for property in self: if property.state == "cancelled": - raise UserError(("Cancelled properties cannot be sold.")) + raise UserError("Cancelled properties cannot be sold.") property.state = "sold" return True - @api.constrains("expected_price","selling_price") + @api.constrains("expected_price", "selling_price") def _check_selling_price(self): for property in self: if not float_is_zero(property.selling_price, precision_digits=2) and ( @@ -107,4 +108,10 @@ def _check_selling_price(self): ) == -1 ): - raise ValidationError("Selling price must be at least 90% of the expected price.") \ No newline at end of file + raise ValidationError("Selling price must be at least 90% of the expected price.") + + @api.ondelete(at_uninstall=False) + def _check_offers_before_delete(self): + for property in self: + if property.state not in ['new', 'cancelled']: + raise UserError("Cannot delete a property that is currently active or sold.") \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8beac17f076..fc8c522b2c8 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -39,7 +39,8 @@ def _inverse_date_deadline(self): if record.date_deadline: record.validity = (record.date_deadline - base_date).days else: - record.validity = 7 + record.validity = 7 + def action_accept_offer(self): for offer in self: if any(status == "accepted" for status in offer.property_id.offer_ids.mapped("status")): @@ -50,12 +51,27 @@ def action_accept_offer(self): offer.property_id.selling_price = offer.price offer.property_id.state = "offer_accepted" return True + def action_reject_offer(self): for offer in self: offer.status = "refused" return True - + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + property_record = self.env['estate.property'].browse(vals.get('property_id')) + + if property_record: + if property_record.offer_ids: + highest_existing_offer = max(property_record.offer_ids.mapped('price')) + + if vals.get('price', 0) < highest_existing_offer: + raise UserError(("The offer price cannot be lower than an existing offer of %s.") % highest_existing_offer) + + property_record.state = 'offer_received' + + return super().create(vals_list) \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 7101a6cc70e..3d9aa8708ec 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -11,4 +11,4 @@ class EstatePropertyTag(models.Model): _check_name_uniq = models.Constraint( "unique (name)", "Each tag name must be unique.", - ) \ No newline at end of file + ) \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index d9af5c5ed83..53c3e8dcccd 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -16,7 +16,9 @@ class EstatePropertyType(models.Model): 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(string="Offers Count", compute="_compute_offer_count") + @api.depends("offer_ids") def _compute_offer_count(self): for record in self: - record.offer_count = len(record.offer_ids) \ No newline at end of file + record.offer_count = len(record.offer_ids) + \ No newline at end of file From 08616400ad9f297cb1b902575e7038d303673527 Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 17:13:33 +0200 Subject: [PATCH 13/20] [ADD] estate: View Inheritance --- estate/models/__init__.py | 2 ++ estate/models/estate_property.py | 6 +++--- estate/models/estate_property_offer.py | 21 +++++++++------------ estate/models/estate_property_tag.py | 2 +- estate/models/estate_property_type.py | 3 +-- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..eb5071cc69b 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,5 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users + diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index af38b7d2704..cdb16225b63 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -82,7 +82,7 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False - + def action_cancel_property(self): for property in self: if property.state == "sold": @@ -109,9 +109,9 @@ def _check_selling_price(self): == -1 ): raise ValidationError("Selling price must be at least 90% of the expected price.") - + @api.ondelete(at_uninstall=False) def _check_offers_before_delete(self): for property in self: if property.state not in ['new', 'cancelled']: - raise UserError("Cannot delete a property that is currently active or sold.") \ No newline at end of file + raise UserError("Cannot delete a property that is currently active or sold.") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index fc8c522b2c8..eec81212db0 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -20,7 +20,7 @@ class EstatePropertyOffer(models.Model): string="Status", copy=False ) - + partner_id = fields.Many2one("res.partner", string="Partner", required=True) property_id = fields.Many2one("estate.property", string="Property", required=True) property_type_id = fields.Many2one("estate.property.type", related="property_id.property_type_id", string="Property Type", store=True) @@ -40,38 +40,35 @@ def _inverse_date_deadline(self): record.validity = (record.date_deadline - base_date).days else: record.validity = 7 - + def action_accept_offer(self): for offer in self: if any(status == "accepted" for status in offer.property_id.offer_ids.mapped("status")): raise UserError("An offer has already been accepted for this property.") - + offer.status = "accepted" offer.property_id.buyer_id = offer.partner_id offer.property_id.selling_price = offer.price offer.property_id.state = "offer_accepted" return True - + def action_reject_offer(self): for offer in self: offer.status = "refused" return True - + @api.model_create_multi def create(self, vals_list): for vals in vals_list: property_record = self.env['estate.property'].browse(vals.get('property_id')) - + if property_record: if property_record.offer_ids: highest_existing_offer = max(property_record.offer_ids.mapped('price')) - + if vals.get('price', 0) < highest_existing_offer: raise UserError(("The offer price cannot be lower than an existing offer of %s.") % highest_existing_offer) - - property_record.state = 'offer_received' - - return super().create(vals_list) + property_record.state = 'offer_received' - \ No newline at end of file + return super().create(vals_list) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 3d9aa8708ec..f76c595736d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -11,4 +11,4 @@ class EstatePropertyTag(models.Model): _check_name_uniq = models.Constraint( "unique (name)", "Each tag name must be unique.", - ) \ No newline at end of file + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 53c3e8dcccd..2b882375ca1 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -16,9 +16,8 @@ class EstatePropertyType(models.Model): 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(string="Offers Count", compute="_compute_offer_count") - + @api.depends("offer_ids") def _compute_offer_count(self): for record in self: record.offer_count = len(record.offer_ids) - \ No newline at end of file From 7744199659c626438dc22bf540dcea9cf9e65992 Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 17:29:27 +0200 Subject: [PATCH 14/20] [FIX] estate: View Inheritance fixe --- estate/models/__init__.py | 1 - estate/models/estate_property_offer.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/estate/models/__init__.py b/estate/models/__init__.py index eb5071cc69b..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,4 +3,3 @@ from . import estate_property_tag from . import estate_property_offer from . import res_users - diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index eec81212db0..b4ccb3128ea 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -40,7 +40,7 @@ def _inverse_date_deadline(self): record.validity = (record.date_deadline - base_date).days else: record.validity = 7 - + def action_accept_offer(self): for offer in self: if any(status == "accepted" for status in offer.property_id.offer_ids.mapped("status")): From bfe00d73aea120d89b11f5aafcbc4389e681ad8a Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 17:32:50 +0200 Subject: [PATCH 15/20] [ADD] estate: extend res.users with salesperson property listings --- estate/models/res_users.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 estate/models/res_users.py diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..6164688ad27 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,11 @@ +from odoo import models, fields + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + "estate.property", + "salesperson_id", + string="Properties", + domain="[('state', 'in', ['new', 'offer_received'])]" + ) \ No newline at end of file From 10fbc5eb02919bf9184ecb26c620a5c07f4a01e2 Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 21:50:29 +0200 Subject: [PATCH 16/20] [FIX] estate: fixes --- estate/models/res_users.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 6164688ad27..d15aa6a5695 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -1,11 +1,12 @@ from odoo import models, fields + class ResUsers(models.Model): _inherit = "res.users" property_ids = fields.One2many( - "estate.property", - "salesperson_id", + "estate.property", + "salesperson_id", string="Properties", - domain="[('state', 'in', ['new', 'offer_received'])]" + domain="[('state', 'in', ['new', 'offer_received'])]", ) \ No newline at end of file From 9185015749e58da383b8aa43d767ff5611a89574 Mon Sep 17 00:00:00 2001 From: amsli Date: Thu, 21 May 2026 21:58:36 +0200 Subject: [PATCH 17/20] [IMP] estate: rename res.users property_ids label to avoid core conflict --- estate/models/res_users.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/estate/models/res_users.py b/estate/models/res_users.py index d15aa6a5695..8a85bfcc2a7 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -4,9 +4,10 @@ class ResUsers(models.Model): _inherit = "res.users" + # FIXED property_ids = fields.One2many( "estate.property", "salesperson_id", - string="Properties", + string="Real Estate Properties", domain="[('state', 'in', ['new', 'offer_received'])]", - ) \ No newline at end of file + ) From 32a261c15c8c5fb2275d6428d0af8eb9f427179b Mon Sep 17 00:00:00 2001 From: amsli Date: Fri, 22 May 2026 10:07:59 +0200 Subject: [PATCH 18/20] [ADD] estate: chpter 12 view enheritance + chapter 14 Kanban View --- estate/__manifest__.py | 1 + estate/views/estate_property_views.xml | 43 +++++++++++++++++++++++++- estate/views/res_users_views.xml | 26 ++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 estate/views/res_users_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 454f03bf55a..b4a2f82b6f4 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ 'views/estate_property_type_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', + 'views/res_users_views.xml', ], 'application': True, } diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4b2be0acf3a..27978747301 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -98,11 +98,52 @@ + + + estate.property.kanban + estate.property + + + + + + + + + +
+ +
+ +
+ Expected Price: + +
+ +
+ Best Offer: + +
+ +
+ Selling Price: + +
+ +
+ +
+ +
+
+
+
+
Properties estate.property - list,form + kanban,list,form {'search_default_available': True} diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..5cae5d4ed0b --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,26 @@ + + + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + + + + + + + + + + \ No newline at end of file From da0e662052f0c95c4759d791757343ff9c8fb722 Mon Sep 17 00:00:00 2001 From: amsli Date: Fri, 22 May 2026 13:28:00 +0200 Subject: [PATCH 19/20] [ADD] estate: chpter 13 estate_account module --- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 10 ++++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 32 ++++++++++++++++++++++++ 4 files changed, 44 insertions(+) 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_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..01de920cf8d --- /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..e2c77130645 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,10 @@ +{ + "name": "Estate Property Invoicing", + "version": "1.0", + "description": "Link module between Real Estate and Accounting apps", + "depends": ["estate", "account"], + "installable": True, + "author": "mila", + "license": "LGPL-3", + 'application': False, +} \ No newline at end of file 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..e39a6c82ee1 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,32 @@ +from odoo import models, fields, Command + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_sold(self): + res = super().action_sold() + + for record in self: + buyer = record.buyer_id.id or record.salesperson_id.id or self.env.user.partner_id.id + + invoice = self.env["account.move"].create({ + "partner_id": buyer, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create({ + "name": f"6% Selling Commission Fee for {record.name}", + "quantity": 1.0, + "price_unit": record.selling_price * 0.06, + }), + Command.create({ + "name": "Administrative Processing Fees", + "quantity": 1.0, + "price_unit": 100.00, + }), + ], + }) + + invoice._compute_amount() + + return res \ No newline at end of file From 7adf16812418a1417de51b647549b25d68b2eac6 Mon Sep 17 00:00:00 2001 From: amsli Date: Fri, 22 May 2026 14:16:46 +0200 Subject: [PATCH 20/20] [FIX] ci/style fixes --- estate_account/__manifest__.py | 2 +- estate_account/models/estate_property.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index e2c77130645..b4a709359d2 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -7,4 +7,4 @@ "author": "mila", "license": "LGPL-3", 'application': False, -} \ No newline at end of file +} diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index e39a6c82ee1..f51decf5efb 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,10 +1,10 @@ -from odoo import models, fields, Command +from odoo import models, Command class EstateProperty(models.Model): _inherit = "estate.property" - def action_sold(self): + def action_sold(self): res = super().action_sold() for record in self: @@ -26,7 +26,7 @@ def action_sold(self): }), ], }) - + invoice._compute_amount() - return res \ No newline at end of file + return res