From c8637b4bd45b1391f2b03cea27789813c1f5d0c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Mon, 15 Dec 2025 16:25:14 +0100 Subject: [PATCH 01/11] [ADD] estate: Create a new module for real estate advertisment with a model set --- estate/__init__.py | 1 + estate/__manifest__.py | 8 ++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 19 +++++++++++++++++++ 4 files changed, 29 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..02e0a36f06f --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,8 @@ +{ + '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..0ac4e554d26 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,19 @@ +from odoo import fields, models + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Advertisement module" + + name = fields.Char('Name', required=True, translate=True) + description = fields.Text('Description') + postcode = fields.Char('Poscode', default='1000') + date_availability = fields.Date('Date availability') + expected_price = fields.Float('Expected price') + selling_price = fields.Float('Selling price') + bedrooms = fields.Integer('Bedrooms') + living_area = fields.Integer('Living Area') + facades = fields.Integer('Facades') + garage = fields.Boolean('Garage') + garden = fields.Boolean('Garden') + garden_area = fields.Integer('Garden Area') + garden_orientation = fields.Selection('Gordden orientation', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) \ No newline at end of file From 7dc2f6eba5b3233f1733b9945eb4cbc791497385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Mon, 15 Dec 2025 17:25:25 +0100 Subject: [PATCH 02/11] [FIX] estate: Fix required field needed in expected price and a typo --- estate/models/estate_property.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 0ac4e554d26..70e1ad7622f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,7 +8,7 @@ class EstateProperty(models.Model): description = fields.Text('Description') postcode = fields.Char('Poscode', default='1000') date_availability = fields.Date('Date availability') - expected_price = fields.Float('Expected price') + expected_price = fields.Float('Expected price', required=True) selling_price = fields.Float('Selling price') bedrooms = fields.Integer('Bedrooms') living_area = fields.Integer('Living Area') @@ -16,4 +16,9 @@ class EstateProperty(models.Model): garage = fields.Boolean('Garage') garden = fields.Boolean('Garden') garden_area = fields.Integer('Garden Area') - garden_orientation = fields.Selection('Gordden orientation', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) \ No newline at end of file + garden_orientation = fields.Selection(selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ], string='Garden orientation') \ No newline at end of file From 7bcf43b67460f8979457518a932f780cee9a48f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Mon, 15 Dec 2025 17:33:21 +0100 Subject: [PATCH 03/11] [ADD] estate: Add some access rights to the db --- estate/__manifest__.py | 3 +++ estate/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 02e0a36f06f..148ed547f7d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,4 +5,7 @@ ], 'installable': True, 'application': True, + '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..1a28315d94f --- /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_real_estate_platform,estate.property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From ab1280b8a9ebe6fccf9446bde1341b71fda358a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Tue, 16 Dec 2025 14:37:26 +0100 Subject: [PATCH 04/11] [IMP] estate: enable UI interaction trough menus, display property data in list view and create/edit property data in form view The model was created and access rights provided but there was no interaction with the model. This is implemented through xml as its is mre convinient for complex data. There is one menu to acces the list view of the properties and a form views is displayed whend creating or editing a property. Some fields have now a default value, some are read-only, some are requiered and some cannot be copied when duplicated. The fields active and state were added for properties display in the list view by default and for it to have a different state depending on the stage respectively. --- estate/__manifest__.py | 3 +++ estate/models/estate_property.py | 24 ++++++++++++++++++------ estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 8 ++++++++ estate/views/estate_property_views.xml | 8 ++++++++ 5 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 148ed547f7d..9551d7334ce 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,9 @@ 'installable': True, 'application': True, 'data': [ + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + 'security/ir.model.access.csv', ] } \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 70e1ad7622f..4a8f593762c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,16 +1,20 @@ from odoo import fields, models +from datetime import timedelta + +def default_availability_date(recordset): + return fields.Datetime.today() + timedelta(days=90) class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Advertisement module" - name = fields.Char('Name', required=True, translate=True) + name = fields.Char('Name', required=True) description = fields.Text('Description') - postcode = fields.Char('Poscode', default='1000') - date_availability = fields.Date('Date availability') + postcode = fields.Char('Postcode') + date_availability = fields.Date('Date availability', copy=False, default=default_availability_date) expected_price = fields.Float('Expected price', required=True) - selling_price = fields.Float('Selling price') - bedrooms = fields.Integer('Bedrooms') + selling_price = fields.Float('Selling price', readonly=True, copy=False) + bedrooms = fields.Integer('Bedrooms', default=2) living_area = fields.Integer('Living Area') facades = fields.Integer('Facades') garage = fields.Boolean('Garage') @@ -21,4 +25,12 @@ class EstateProperty(models.Model): ('south', 'South'), ('east', 'East'), ('west', 'West') - ], string='Garden orientation') \ No newline at end of file + ], string='Garden orientation') + active = fields.Boolean('Active' ,default=True) + state = fields.Selection(selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], string="State", default='new', required=True, copy=False) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 1a28315d94f..50e2e32682c 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 -access_real_estate_platform,estate.property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_real_estate_platform,estate_property,model_estate_property,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 new file mode 100644 index 00000000000..698b4b952a9 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..affda3b8abc --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate.property + list,form + + \ No newline at end of file From a76b9fe6e07e67e8e22e6fa571f0cee4b0df47f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Wed, 17 Dec 2025 13:41:52 +0100 Subject: [PATCH 05/11] [IMP] estate: enrich the different views (list, form, and search) of the estate module. The views created were basic one and need some custumization to be use in a business application. List view: the following fields have been added: Title, Postcode, bedrooms, Living Area (sqm), Expected Price, Selling Price, Available From Form view: It is now composed of groups and a notebook with a page description Search view: Search has now other predifened shorcuts (Title, Postcode, expectedPrice, Bedrooms, Living Area, Facades), a predefined filter "Available" (that looks for properties with the state New or Offer received) and a Group by filter on the postcodes --- estate/views/estate_property_views.xml | 77 ++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index affda3b8abc..b77681e6c1b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,82 @@ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + + estate.property.list + estate.property + + + + + + + + + + + + + Properties estate.property From 5b6bde87c07936e0be10b8080b7c59aa894e1b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Thu, 18 Dec 2025 16:39:35 +0100 Subject: [PATCH 06/11] [IMP] estate: introduce various new data in the estate property model: buyer, salesperson, property type, list of tags and list of offers. Buyer Can be any individual (res.partner) Salesperson Has to be an employe (res.user) Property type New model with the name of the type (estate.property.type) Tags New model with the name of the tag (estate.property.tag) Offers New model with the buyer, price, status and property id (estate.property.offer) All those new models have list and form view but the offer model is only accessible from the porperty form (as each offer is linked to a property) A new menu setting has been added to allow the creation of new tags and types. --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 7 ++++- estate/models/estate_property_offer.py | 13 +++++++++ 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 | 4 +++ estate/views/estate_property_offer_views.xml | 30 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 22 ++++++++++++++ estate/views/estate_property_type_views.xml | 22 ++++++++++++++ estate/views/estate_property_views.xml | 18 +++++++++++- 12 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 9551d7334ce..c880174a961 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,9 @@ 'installable': True, 'application': True, 'data': [ + 'views/estate_property_offer_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..aa4dade4194 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4a8f593762c..b18deadd059 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -33,4 +33,9 @@ class EstateProperty(models.Model): ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled') - ], string="State", default='new', required=True, copy=False) \ No newline at end of file + ], string="State", default='new', required=True, copy=False) + property_type_id = fields.Many2one('estate.property.type', string="Property type") + property_buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) + property_salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) + property_tag_ids = fields.Many2many('estate.property.tag', string="Property tags") + property_offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..e002ab18712 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,13 @@ +from odoo import fields, models + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "A property offer" + + price = fields.Float('Price') + status = fields.Selection(selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ], copy=False, string='Status') + property_buyer_id = fields.Many2one('res.partner', string="Buyer", 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..e64cddda5bb --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "A property tag" + + name = fields.Char('Property tag', 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..032a4f3ef1c --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "A property type is, for example, a house or an apartment. It is a standard business need to categorize properties according to their type, especially to refine filtering." + + name = fields.Char('Property type', 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 50e2e32682c..ebd47619cc5 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_real_estate_platform,estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_real_estate_platform,estate_property,model_estate_property,base.group_user,1,1,1,1 +access_real_estate_type,estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_real_estate_tag,estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_real_estate_offer,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 698b4b952a9..d2db06f2618 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,5 +4,9 @@ + + + +
\ 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..9fceac67fbb --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,30 @@ + + + + estate.property.offer.form + estate.property.offer + +
+ + + + + + + +
+
+
+ + + estate.property.offer.list + 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..74e4501f5cb --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,22 @@ + + + + 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..c72829b86d6 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,22 @@ + + + + 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 b77681e6c1b..90b75b77937 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,6 +6,7 @@ + @@ -28,9 +29,14 @@

+ + + + + @@ -39,6 +45,7 @@ + @@ -52,7 +59,15 @@ - + + + + + + + + + @@ -67,6 +82,7 @@ + From 4a9c637bfb4f10d3dc10eef9a4e7d7bae1dca422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Fri, 19 Dec 2025 11:31:33 +0100 Subject: [PATCH 07/11] [IMP] estate: enrich the models with new fields (best price and total area in estate property and validity and deadline in property offer). Best price is computed on the maximum price of all the offers linked to the property. Total area is computed as the sum of the lving area and garden area. Validity is the number days that have passed since the creation of the offer during wich it remains valid. Deadline is the date until wich the offer remains valid. It is computed based on validity and when changed, updates validity. --- estate/models/estate_property.py | 28 +++++++++++++++++--- estate/models/estate_property_offer.py | 18 +++++++++++-- estate/views/estate_property_offer_views.xml | 6 ++++- estate/views/estate_property_views.xml | 2 ++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b18deadd059..787a91ae4f1 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,8 +1,8 @@ -from odoo import fields, models +from odoo import api, fields, models from datetime import timedelta def default_availability_date(recordset): - return fields.Datetime.today() + timedelta(days=90) + return fields.Date.context_today(recordset) + timedelta(days=90) class EstateProperty(models.Model): _name = "estate.property" @@ -26,6 +26,7 @@ class EstateProperty(models.Model): ('east', 'East'), ('west', 'West') ], string='Garden orientation') + total_area = fields.Integer('Total Area', compute='_compute_total_area') active = fields.Boolean('Active' ,default=True) state = fields.Selection(selection=[ ('new', 'New'), @@ -38,4 +39,25 @@ class EstateProperty(models.Model): property_buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) property_salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) property_tag_ids = fields.Many2many('estate.property.tag', string="Property tags") - property_offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') \ No newline at end of file + property_offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + best_price = fields.Float('Best Price', 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('property_offer_ids.price') + def _compute_best_price(self): + for record in self: + offer_prices = record.property_offer_ids.mapped('price') + record.best_price = max(offer_prices) if offer_prices else 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 = '' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e002ab18712..0c1f2f753ea 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,5 @@ -from odoo import fields, models +from odoo import api, fields, models +from dateutil.relativedelta import relativedelta class EstatePropertyOffer(models.Model): _name = "estate.property.offer" @@ -10,4 +11,17 @@ class EstatePropertyOffer(models.Model): ('refused', 'Refused') ], copy=False, string='Status') property_buyer_id = fields.Many2one('res.partner', string="Buyer", 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('Validity (Days)', default=7) + date_deadline = fields.Date('Deadline', compute='_compute_date_deadline', inverse='_inverse_validity') + + @api.depends('validity') + def _compute_date_deadline(self): + for record in self: + starting_date = record.create_date if record.create_date else fields.Date.context_today(self) + record.date_deadline = starting_date + relativedelta(days=record.validity) + + def _inverse_validity(self): + for record in self: + starting_date = fields.Date.to_date(record.create_date) if record.create_date else fields.Date.context_today(self) + record.validity = (record.date_deadline - starting_date).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 9fceac67fbb..5ce04bab446 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,8 +8,10 @@ - + + + @@ -23,6 +25,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 90b75b77937..da77f29d73b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -42,6 +42,7 @@ + @@ -57,6 +58,7 @@ + From 5dafd8bc393646677381815d5c82760cbf0eae33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Fri, 19 Dec 2025 11:46:28 +0100 Subject: [PATCH 08/11] [LINT] estate: format file of estate module to remove linting issues. Previewsly made pull request could not pass because of the linting issues. --- estate/__init__.py | 2 +- estate/__manifest__.py | 6 ++++-- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 4 +++- estate/models/estate_property_offer.py | 1 + estate/models/estate_property_tag.py | 3 ++- estate/models/estate_property_type.py | 3 ++- 7 files changed, 14 insertions(+), 7 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 c880174a961..1b58f554d52 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -13,5 +13,7 @@ 'views/estate_menus.xml', 'security/ir.model.access.csv', - ] -} \ No newline at end of file + ], + 'author': 'Odoo S.A.', + 'license': 'LGPL-3', +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index aa4dade4194..f2347942227 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer -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 787a91ae4f1..678839c3041 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,11 @@ from odoo import api, fields, models from datetime import timedelta + def default_availability_date(recordset): return fields.Date.context_today(recordset) + timedelta(days=90) + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Advertisement module" @@ -27,7 +29,7 @@ class EstateProperty(models.Model): ('west', 'West') ], string='Garden orientation') total_area = fields.Integer('Total Area', compute='_compute_total_area') - active = fields.Boolean('Active' ,default=True) + active = fields.Boolean('Active', default=True) state = fields.Selection(selection=[ ('new', 'New'), ('offer_received', 'Offer Received'), diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 0c1f2f753ea..71ee0d95fb8 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from odoo import api, fields, models from dateutil.relativedelta import relativedelta + class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "A property offer" diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index e64cddda5bb..a1697f95ca3 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,7 +1,8 @@ from odoo import fields, models + class EstatePropertyTag(models.Model): _name = "estate.property.tag" _description = "A property tag" - name = fields.Char('Property tag', required=True) \ No newline at end of file + name = fields.Char('Property tag', required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 032a4f3ef1c..a632cd73c74 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,7 +1,8 @@ from odoo import fields, models + class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "A property type is, for example, a house or an apartment. It is a standard business need to categorize properties according to their type, especially to refine filtering." - name = fields.Char('Property type', required=True) \ No newline at end of file + name = fields.Char('Property type', required=True) From 1d101e8321e830e6efd89b93f7d9396008b0b5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steven=20Fran=C3=A7ois=20=28stfra=29?= Date: Fri, 19 Dec 2025 17:28:34 +0100 Subject: [PATCH 09/11] [IMP] estate: allow more direct interactions with the property and its offers. The state of a property and its offer could only be changed manually which is not safe way of handling those status. Now buttons have been added : sold and cancel for the property and accept and refuse for the offers. An error pop-up is shown when trying to perfom an illegal action. --- estate/models/estate_property.py | 23 +++++++++++++++++++- estate/models/estate_property_offer.py | 22 +++++++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 5 +++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 678839c3041..0c74ff5a632 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,6 @@ from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.tools.translate import _ from datetime import timedelta @@ -41,7 +43,7 @@ class EstateProperty(models.Model): property_buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False) property_salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) property_tag_ids = fields.Many2many('estate.property.tag', string="Property tags") - property_offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers') + property_offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers', inverse='_inverse_offers') best_price = fields.Float('Best Price', compute='_compute_best_price') @api.depends('living_area', 'garden_area') @@ -63,3 +65,22 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = '' + + def _inverse_offers(self): + for record in self: + if record.state == 'new' and record.property_offer_ids: + record.state = 'offer_received' + + def action_sold(self): + for record in self: + if record.state == 'cancelled': + raise UserError(_('Cancelled properties cannot be sold.')) + record.state = 'sold' + return True + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError(_('Sold properties cannot be cancelled.')) + record.state = 'cancelled' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 71ee0d95fb8..08c0db2ef1c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ from odoo import api, fields, models +from odoo.exceptions import UserError +from odoo.tools.translate import _ from dateutil.relativedelta import relativedelta @@ -26,3 +28,23 @@ def _inverse_validity(self): for record in self: starting_date = fields.Date.to_date(record.create_date) if record.create_date else fields.Date.context_today(self) record.validity = (record.date_deadline - starting_date).days + + def action_accept(self): + for record in self: + match record.property_id.state: + case 'offer_accepted': + raise UserError(_('This property already has an offer accepted')) + case 'sold': + raise UserError(_('Sold properties cannot accept offers')) + case 'cancelled': + raise UserError(_('Cancelled properties cannot accept other offers')) + record.status = 'accepted' + record.property_id.state = 'offer_accepted' + record.property_id.property_buyer_id = record.property_buyer_id + record.property_id.selling_price = record.price + + def action_refuse(self): + for record in self: + record.status = 'refused' + record.property_id.property_buyer_id = None + record.property_id.selling_price = 0 diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 5ce04bab446..a5da2365068 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -27,6 +27,8 @@ +