From 8bc857f8b017c329e9571583daf5af80efb0ba2e Mon Sep 17 00:00:00 2001 From: 4yuub Date: Tue, 19 May 2026 16:33:21 +0200 Subject: [PATCH 01/10] [ADD] : onboarding Server framework 101 chapter 1 to 5 --- estate/__init__.py | 1 + estate/__manifest__.py | 24 +++++++++++++++ estate/models/__init__.py | 1 + estate/models/real_estate.py | 41 ++++++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 9 ++++++ estate/views/estate_property_views.xml | 10 +++++++ 7 files changed, 88 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/real_estate.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/__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..43c203e0e9a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,24 @@ +{ + 'name': "Real Estate", + 'version': '1.0', + 'depends': ['base'], + 'author': "Odoo S.A. (aykar)", + 'category': 'Customizations', + 'description': """ + This module is designed to manage real estate properties, including details such as property type, location, price, and status. + + It allows users to easily track and organize their real estate assets, making it easier to manage and analyze their property portfolio. + """, + 'installable': True, + 'application': True, + # data files always loaded at installation + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', + ], + # data files containing optionally loaded demonstration data + 'demo': [ + # 'demo/demo_data.xml', + ], +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..05018e037ce --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import real_estate diff --git a/estate/models/real_estate.py b/estate/models/real_estate.py new file mode 100644 index 00000000000..a91a1f12596 --- /dev/null +++ b/estate/models/real_estate.py @@ -0,0 +1,41 @@ +from odoo import models, fields +from datetime import date +from dateutil.relativedelta import relativedelta + + +def three_months_from_now(_model): + return date.today() + relativedelta(months=3) + + +class EstateProperty(models.Model): + _name = "estate_property" + _description = "Real Estate Property Model" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(copy=False, default=three_months_from_now) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection([ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ]) + + # pre reseved: https://www.odoo.com/documentation/19.0/developer/reference/backend/orm.html#reference-orm-fields-reserved + active = fields.Boolean(default=True) + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Canceled'), + ], default='new') diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..907b58782a5 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,9 @@ + + + + + + + + \ 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..780eea413c0 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,10 @@ + + + + + Properties + estate_property + list,form + + + \ No newline at end of file From 4bb98e2ed1461c4eac960715084eebafdd9325fd Mon Sep 17 00:00:00 2001 From: 4yuub Date: Tue, 19 May 2026 17:23:16 +0200 Subject: [PATCH 02/10] [REF] : fix styling in manifest --- estate/__manifest__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 43c203e0e9a..181112b1003 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,11 +6,12 @@ 'category': 'Customizations', 'description': """ This module is designed to manage real estate properties, including details such as property type, location, price, and status. - + It allows users to easily track and organize their real estate assets, making it easier to manage and analyze their property portfolio. """, 'installable': True, 'application': True, + 'liscence': 'LGPL-3', # data files always loaded at installation 'data': [ 'security/ir.model.access.csv', From 9fec86c306193205401313ee03b19bacfd03bd6f Mon Sep 17 00:00:00 2001 From: 4yuub Date: Wed, 20 May 2026 10:46:59 +0200 Subject: [PATCH 03/10] [ADD] : Basic Views Chapter 6 --- estate/models/real_estate.py | 2 +- estate/views/estate_property_views.xml | 82 ++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/estate/models/real_estate.py b/estate/models/real_estate.py index a91a1f12596..59a576b8d1d 100644 --- a/estate/models/real_estate.py +++ b/estate/models/real_estate.py @@ -38,4 +38,4 @@ class EstateProperty(models.Model): ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('canceled', 'Canceled'), - ], default='new') + ], required=True, default='new') diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 780eea413c0..32d5e5fc1ea 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,5 +6,87 @@ estate_property list,form + + + Properties List + estate_property + + + + + + + + + + + + + + + Properties Search + estate_property + + + + + + + + + + + + + + + + Property Form + estate_property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
\ No newline at end of file From 1d006b20205f3c6933b856b1033207e3ec6c91d6 Mon Sep 17 00:00:00 2001 From: 4yuub Date: Wed, 20 May 2026 13:57:24 +0200 Subject: [PATCH 04/10] [ADD] estate: relations between models chapter 7 --- estate/__init__.py | 1 + estate/__manifest__.py | 1 + estate/models/__init__.py | 6 ++- .../{real_estate.py => estate_property.py} | 12 ++++- estate/models/estate_property_offer.py | 16 +++++++ estate/models/estate_property_tag.py | 8 ++++ estate/models/estate_property_type.py | 8 ++++ estate/security/ir.model.access.csv | 5 +- estate/views/estate_menus.xml | 11 +++-- estate/views/estate_property_offers_views.xml | 32 +++++++++++++ estate/views/estate_property_views.xml | 46 +++++++++++++------ 11 files changed, 126 insertions(+), 20 deletions(-) rename estate/models/{real_estate.py => estate_property.py} (74%) 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_offers_views.xml diff --git a/estate/__init__.py b/estate/__init__.py index 0650744f6bc..be2e12feb05 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ +# flake8: noqa: F401 from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 181112b1003..eef0530239b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -16,6 +16,7 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_offers_views.xml', 'views/estate_menus.xml', ], # data files containing optionally loaded demonstration data diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 05018e037ce..7dd81120c18 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,5 @@ -from . import real_estate +# flake8: noqa: F401 +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/real_estate.py b/estate/models/estate_property.py similarity index 74% rename from estate/models/real_estate.py rename to estate/models/estate_property.py index 59a576b8d1d..b9094e619dc 100644 --- a/estate/models/real_estate.py +++ b/estate/models/estate_property.py @@ -8,7 +8,7 @@ def three_months_from_now(_model): class EstateProperty(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "Real Estate Property Model" name = fields.Char(required=True) @@ -30,6 +30,16 @@ class EstateProperty(models.Model): ('west', 'West'), ]) + # fk + property_type_id = fields.Many2one( + "estate.property.type", string="Property Type") + salesman_id = fields.Many2one( + "res.partner", string="Salesman", default=lambda self: self.env.user) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) + + tag_ids = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + # pre reseved: https://www.odoo.com/documentation/19.0/developer/reference/backend/orm.html#reference-orm-fields-reserved active = fields.Boolean(default=True) state = fields.Selection([ diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..ce1aba8586e --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,16 @@ +from odoo import models, fields + + +class EstatePropertyOffer(models.Model): + _name = 'estate.property.offer' + _description = "Real Estate Property Offer Model" + + price = fields.Float() + status = fields.Selection([ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], copy=False) + + # fk + partner_id = fields.Many2one("res.partner", required=True) + property_id = fields.Many2one("estate.property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..e3d9b2dc9d9 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = "Real Estate Property Tag Model" + + 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..bb1597b2dea --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = "Real Estate Property Type Model" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..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 -access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 907b58782a5..714be3f129b 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,9 +1,14 @@ - - + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml new file mode 100644 index 00000000000..f8085c53273 --- /dev/null +++ b/estate/views/estate_property_offers_views.xml @@ -0,0 +1,32 @@ + + + + + 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_views.xml b/estate/views/estate_property_views.xml index 32d5e5fc1ea..9bcf37ae30c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,13 +3,25 @@ Properties - estate_property + estate.property + list,form + + + + Property Types + estate.property.type + list,form + + + + Property Tags + estate.property.tag list,form - Properties List - estate_property + estate.property.list + estate.property @@ -24,8 +36,8 @@ - Properties Search - estate_property + estate.property.search + estate.property @@ -43,29 +55,26 @@ - Property Form - estate_property + estate.property.form + estate.property
-
+

+
+ + - - - - - - @@ -82,6 +91,15 @@ + + + + + + + + + From cd49a2b7a93a5144e0170ea02ae20c5f15e04b2b Mon Sep 17 00:00:00 2001 From: 4yuub Date: Wed, 20 May 2026 15:09:48 +0200 Subject: [PATCH 05/10] [ADD] estate: computed fields and onchanges chapter 8 --- estate/models/estate_property.py | 31 +++++++++++++++++-- estate/models/estate_property_offer.py | 23 +++++++++++++- estate/views/estate_property_offers_views.xml | 4 +++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b9094e619dc..fe0e32ebc34 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 @@ -38,7 +38,8 @@ class EstateProperty(models.Model): buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) tag_ids = fields.Many2many("estate.property.tag") - offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + offer_ids = fields.One2many( + "estate.property.offer", "property_id", string="Offers") # pre reseved: https://www.odoo.com/documentation/19.0/developer/reference/backend/orm.html#reference-orm-fields-reserved active = fields.Boolean(default=True) @@ -49,3 +50,29 @@ class EstateProperty(models.Model): ('sold', 'Sold'), ('canceled', 'Canceled'), ], required=True, default='new') + + # computed fields + + total_area = fields.Integer(compute='_compute_total_area') + best_price = fields.Float(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: + record.best_price = max(record.offer_ids.mapped('price')) + + # onchange + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + return + self.garden_area = None + self.garden_orientation = None diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index ce1aba8586e..fc046c353c0 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import models, fields +from odoo import models, fields, api +from datetime import date +from dateutil.relativedelta import relativedelta class EstatePropertyOffer(models.Model): @@ -10,7 +12,26 @@ class EstatePropertyOffer(models.Model): ('accepted', 'Accepted'), ('refused', 'Refused'), ], copy=False) + validity = fields.Integer(default=7) # fk partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True) + + # computed fields + date_deadline = fields.Date( + compute='_compute_deadline_date', inverse='_inverse_deadline_date') + + @api.depends('validity') + def _compute_deadline_date(self): + for record in self: + record.date_deadline = ( + record.create_date or date.today()) + \ + relativedelta(days=record.validity) + + def _inverse_deadline_date(self): + for record in self: + start_date = record.create_date.date() if record.create_date \ + else date.today() + + record.validity = (record.date_deadline - start_date).days diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml index f8085c53273..f10f1ac2df1 100644 --- a/estate/views/estate_property_offers_views.xml +++ b/estate/views/estate_property_offers_views.xml @@ -8,6 +8,8 @@ + + @@ -22,6 +24,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9bcf37ae30c..125e6694cf6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -76,6 +76,7 @@ + @@ -89,6 +90,7 @@ + From 8b9fb3980d4d08365c0623dfb9f21e06a348eced Mon Sep 17 00:00:00 2001 From: 4yuub Date: Wed, 20 May 2026 16:39:21 +0200 Subject: [PATCH 06/10] [ADD] estate: Actions chapter 9 --- estate/models/estate_property.py | 29 +++++++++++++++++-- estate/models/estate_property_offer.py | 22 +++++++++++++- estate/views/estate_property_offers_views.xml | 2 ++ estate/views/estate_property_views.xml | 7 +++++ 4 files changed, 57 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index fe0e32ebc34..e1598e3cc44 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, exceptions from datetime import date from dateutil.relativedelta import relativedelta @@ -64,7 +64,8 @@ def _compute_total_area(self): @api.depends('offer_ids.price') def _compute_best_price(self): for record in self: - record.best_price = max(record.offer_ids.mapped('price')) + record.best_price = max(record.offer_ids.mapped( + 'price')) if record.offer_ids else None # onchange @@ -76,3 +77,27 @@ def _onchange_garden(self): return self.garden_area = None self.garden_orientation = None + + # actions + def action_set_sold(self): + error = None + for record in self: + if record.state == 'canceled': + error = 'Canceld properties cannot be sold' + continue + record.state = 'sold' + if error: + raise exceptions.UserError(error) + print("Sold with success") + return True + + def action_set_canceled(self): + error = None + for record in self: + if record.state == 'sold': + error = 'Sold properties cannot be canceled' + continue + record.state = 'canceled' + if error: + raise exceptions.UserError(error) + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index fc046c353c0..2d5d05f1cf9 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, exceptions from datetime import date from dateutil.relativedelta import relativedelta @@ -35,3 +35,23 @@ def _inverse_deadline_date(self): else date.today() record.validity = (record.date_deadline - start_date).days + + # actions + def action_accept(self): + for record in self: + for offer in record.property_id.offer_ids: + if offer.status == 'accepted' and record.id != offer.id: + raise exceptions.UserError( + 'A property can only have one accpeted Offer') + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + + return True + + def action_refuse(self): + for record in self: + record.status = 'refused' + record.property_id.selling_price = None + record.property_id.buyer_id = None + return True diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml index f10f1ac2df1..8ef22c2f668 100644 --- a/estate/views/estate_property_offers_views.xml +++ b/estate/views/estate_property_offers_views.xml @@ -10,6 +10,8 @@ + +
+ +
+

+ +

+
+ + + + + + + + + + + +
+ +
+
+
+ \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 281411f8f5f..a5b73aa567d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,32 +5,26 @@ Properties estate.property list,form - - - - Property Types - estate.property.type - list,form - - - - Property Tags - estate.property.tag - list,form + {'search_default_available': True} estate.property.list estate.property - + + - + @@ -44,7 +38,8 @@ - + @@ -60,9 +55,13 @@
-
@@ -70,13 +69,14 @@

- + - - + @@ -95,13 +95,15 @@ - - + + - + From 1ce2b27acce2249d47c4252f88c10e5d90e68fb9 Mon Sep 17 00:00:00 2001 From: 4yuub Date: Thu, 21 May 2026 16:03:15 +0200 Subject: [PATCH 10/10] [REF] estate: trying to match odoo coding guidelines --- estate/__init__.py | 1 - estate/__manifest__.py | 7 +- estate/models/__init__.py | 7 +- estate/models/estate_property.py | 70 ++++++------- estate/models/estate_property_offer.py | 52 +++++----- estate/models/estate_property_tag.py | 5 +- estate/models/estate_property_type.py | 23 ++--- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 22 ++-- estate/views/estate_property_offer_views.xml | 90 ++++++++--------- estate/views/estate_property_tag_views.xml | 72 ++++++------- estate/views/estate_property_type_views.xml | 100 +++++++++---------- estate/views/estate_property_views.xml | 8 +- 13 files changed, 216 insertions(+), 243 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index be2e12feb05..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,2 +1 @@ -# flake8: noqa: F401 from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 153db19f459..d048bae867f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,7 @@ 'name': "Real Estate", 'version': '1.0', 'depends': ['base'], - 'author': "Odoo S.A. (aykar)", + 'author': "Odoo S.A.", 'category': 'Customizations', 'description': """ This module is designed to manage real estate properties, including details such as property type, location, price, and status. @@ -12,7 +12,6 @@ 'installable': True, 'application': True, 'license': 'LGPL-3', - # data files always loaded at installation 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', @@ -21,8 +20,4 @@ 'views/estate_property_tag_views.xml', 'views/estate_menus.xml', ], - # data files containing optionally loaded demonstration data - 'demo': [ - # 'demo/demo_data.xml', - ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index d6da8b2d8c9..aae114c2505 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,5 +1,2 @@ -# flake8: noqa: F401 -from . import estate_property -from . import estate_property_type -from . import estate_property_tag -from . import estate_property_offer +from . import (estate_property, estate_property_offer, estate_property_tag, + estate_property_type) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bdcccfb9a4f..28187f5e60d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,11 +1,8 @@ -from odoo import models, fields, api, exceptions -from odoo.tools import float_compare, float_is_zero from datetime import date from dateutil.relativedelta import relativedelta - -def three_months_from_now(_model): - return date.today() + relativedelta(months=3) +from odoo import _, api, exceptions, fields, models +from odoo.tools import float_compare, float_is_zero class EstateProperty(models.Model): @@ -16,7 +13,8 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False, default=three_months_from_now) + date_availability = fields.Date( + copy=False, default=lambda x: date.today() + relativedelta(months=3)) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -32,27 +30,38 @@ class EstateProperty(models.Model): ('west', 'West'), ]) - # fk property_type_id = fields.Many2one( - "estate.property.type", string="Property Type") + "estate.property.type", string="Property Type" + ) salesman_id = fields.Many2one( - "res.partner", string="Salesman", default=lambda self: self.env.user) + "res.partner", string="Salesman", default=lambda self: self.env.user + ) buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) tag_ids = fields.Many2many("estate.property.tag") offer_ids = fields.One2many( - "estate.property.offer", "property_id", string="Offers") + "estate.property.offer", "property_id", string="Offers" + ) + active = fields.Boolean(default=True) + state = fields.Selection([ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], required=True, default='new') + sequence = fields.Integer(default=1) + total_area = fields.Integer(compute='_compute_total_area') + best_price = fields.Float(compute='_compute_best_price') - # constrains _check_expected_price = models.Constraint( 'CHECK(expected_price > 0)', - 'The expected price must be stricly positive' + 'The expected price must be stricly positive.' ) _check_selling_price = models.Constraint( - # double checking (value comes from offer price) 'CHECK(selling_price >= 0)', - 'The selling price must be stricly positive' + 'The selling price must be stricly positive.' ) @api.constrains('selling_price') @@ -63,23 +72,7 @@ def _check_selling_price_py(self): record.expected_price * .9, precision_digits=2) < 0: raise exceptions.ValidationError( - "The selling price can't be lower than 90%% of the expected price") # noqa: E501 - - # pre reseved: https://www.odoo.com/documentation/19.0/developer/reference/backend/orm.html#reference-orm-fields-reserved - active = fields.Boolean(default=True) - state = fields.Selection([ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('cancelled', 'Cancelled'), - ], required=True, default='new') - sequence = fields.Integer(default=1) - - # computed fields - - total_area = fields.Integer(compute='_compute_total_area') - best_price = fields.Float(compute='_compute_best_price') + "The selling price can't be lower than 90%% of the expected price.") # noqa: E501 @api.depends('living_area', 'garden_area') def _compute_total_area(self): @@ -92,42 +85,35 @@ def _compute_best_price(self): record.best_price = max(record.offer_ids.mapped( 'price')) if record.offer_ids else None - # onchange - @api.onchange('garden') def _onchange_garden(self): + self.garden_area = None + self.garden_orientation = None if self.garden: self.garden_area = 10 self.garden_orientation = 'north' - return - self.garden_area = None - self.garden_orientation = None @api.onchange('offer_ids') def _onchange_offer_ids(self): if self.offer_ids and self.state == 'new': self.state = 'offer_received' - # actions - def action_set_sold(self): error = None for record in self: if record.state == 'cancelled': - error = 'Cancelled properties cannot be sold' + error = _('Cancelled properties cannot be sold') continue record.state = 'sold' if error: raise exceptions.UserError(error) - return True def action_set_cancelled(self): error = None for record in self: if record.state == 'sold': - error = 'Sold properties cannot be cancelled' + error = _('Sold properties cannot be cancelled') continue record.state = 'cancelled' if error: raise exceptions.UserError(error) - return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9bda399f786..08f81b6166f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,7 +1,8 @@ -from odoo import models, fields, api, exceptions from datetime import date from dateutil.relativedelta import relativedelta +from odoo import api, exceptions, fields, models + class EstatePropertyOffer(models.Model): _name = "estate.property.offer" @@ -9,59 +10,58 @@ class EstatePropertyOffer(models.Model): _order = "price desc" price = fields.Float() - status = fields.Selection([ - ('accepted', 'Accepted'), - ('refused', 'Refused'), - ], copy=False) + status = fields.Selection( + selection=[ + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], copy=False + ) validity = fields.Integer(default=7) - # fk partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True) property_type_id = fields.Many2one( - related='property_id.property_type_id', stored=True) + related='property_id.property_type_id', stored=True + ) + date_deadline = fields.Date( + compute='_compute_deadline_date', inverse='_inverse_deadline_date' + ) - # constraints _check_offer_price = models.Constraint( 'CHECK(price > 0)', - 'The offer price must be stricly positive' + 'The offer price must be stricly positive.' ) - # computed fields - date_deadline = fields.Date( - compute='_compute_deadline_date', inverse='_inverse_deadline_date') - @api.depends('validity') def _compute_deadline_date(self): for record in self: - record.date_deadline = ( - record.create_date or date.today()) + \ - relativedelta(days=record.validity) + record.date_deadline = (record.create_date or date.today()) + relativedelta( + days=record.validity + ) def _inverse_deadline_date(self): for record in self: - start_date = record.create_date.date() if record.create_date \ - else date.today() + start_date = ( + record.create_date.date() if record.create_date else date.today() + ) record.validity = (record.date_deadline - start_date).days - # actions def action_accept(self): for record in self: - for offer in record.property_id.offer_ids: - if offer.status == 'accepted' and record.id != offer.id: - raise exceptions.UserError( - 'A property can only have one accpeted Offer') + if any( + offer.status == "accepted" and record.id != offer.id + for offer in record.property_id.offer_ids + ): + raise exceptions.UserError( + 'A property can only have one accpeted Offer') record.status = 'accepted' record.property_id.selling_price = record.price record.property_id.buyer_id = record.partner_id record.property_id.state = 'offer_accepted' - return True - def action_refuse(self): for record in self: record.status = 'refused' record.property_id.selling_price = None record.property_id.buyer_id = None - return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 898f6e43d55..e27bfc3324b 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import fields, models class EstatePropertyTag(models.Model): @@ -9,8 +9,7 @@ class EstatePropertyTag(models.Model): name = fields.Char(required=True) color = fields.Integer() - # constraints _check_unique_name = models.Constraint( 'UNIQUE(name)', - 'Tag name must be unique' + 'Tag name must be unique.' ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index d31dc91c85a..98bbf105ce2 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import api, fields, models class EstatePropertyType(models.Model): @@ -7,23 +7,20 @@ class EstatePropertyType(models.Model): _order = 'name' name = fields.Char(required=True) - - # fk property_ids = fields.One2many( - 'estate.property', 'property_type_id', string="Properties") + 'estate.property', 'property_type_id', string="Properties" + ) offer_ids = fields.One2many( - 'estate.property.offer', 'property_type_id', string="Offers") - - # computed + 'estate.property.offer', 'property_type_id', string="Offers" + ) offer_count = fields.Integer(compute='_compute_offer_count') + _check_unique_name = models.Constraint( + 'UNIQUE(name)', + 'Type name must be unique.' + ) + @api.depends('offer_ids') def _compute_offer_count(self): for record in self: record.offer_count = len(record.offer_ids or []) - - # constraints - _check_unique_name = models.Constraint( - 'UNIQUE(name)', - 'Type name must be unique' - ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 05bd9eefba4..89f97c50842 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 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 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 714be3f129b..0934669595c 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,14 +1,14 @@ - - - + + + + + + + + - - - - - - \ 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 7eb63601f4b..5ed1513c447 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,49 +1,49 @@ - - - Property Offer - estate.property.offer - list,form - [('property_type_id', '=', active_id)] - + + + Property Offer + estate.property.offer + list,form + [('property_type_id', '=', active_id)] + - - estate.property.offer.list - estate.property.offer - - - - - - - - + + estate.property.type.form + estate.property.type + + + +
+ +
-
-

- -

-
- - - - - - - - - - - -
- -
-
-
-
\ No newline at end of file +
+

+ +

+
+ + + + + + + + + + + +
+ +
+ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a5b73aa567d..5ba78998567 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -33,7 +33,7 @@ estate.property.search estate.property - + @@ -58,7 +58,7 @@