From 870000896749c118ac8e0b55e0e1c53b908eabac Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Tue, 19 May 2026 13:31:38 +0200 Subject: [PATCH 01/12] update: chapter 4 done --- estate/__init__.py | 3 ++ estate/__manifest__.py | 12 ++++++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 61 +++++++++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 + 5 files changed, 81 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 create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..5305644df14 --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +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..a185ac3084a --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Estate", + 'depends': ['base'], + 'application': True, + 'installable': True, + "author": "daloe", + "license": "LGPL-3", + "data": [ + 'security/ir.model.access.csv', + ], +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f2db223b7d4 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +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..25b4fcea520 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,61 @@ +from odoo import models, fields + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "technical training estate property model" + + name = fields.Char( + "Estate Property", + required = True, + ) + + description = fields.Text( + "Description" + ) + + postcode = fields.Char( + "Post Code" + ) + + date_availability = fields.Date( + "Date Available" + ) + + expected_price = fields.Float( + "Expected Price", + required = True, + ) + + selling_price = fields.Float( + "Selling Price" + ) + + bedrooms = fields.Integer( + "Bedrooms" + ) + + living_area = fields.Integer( + "Living Area" + ) + + facades = fields.Integer( + "Facades" + ) + + garage = fields.Boolean( + "Garage", + default=False, + ) + + garden = fields.Boolean( + "Garden", + default=False, + ) + + garden_area = fields.Integer( + "Garden Area" + ) + + garden_orientation = fields.Selection( + [["north", "North"],["east", "East"], ["south", "South"], ["west", "West"]], + ) \ 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..0e11f47e58d --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From cfd430bc5a3f020f7a7513f58ea6c5ddac7bded8 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Tue, 19 May 2026 14:39:52 +0200 Subject: [PATCH 02/12] update: chapter 5 UI done --- estate/__manifest__.py | 3 +++ estate/models/estate_property.py | 22 +++++++++++++++++++--- estate/views/estate_menus.xml | 9 +++++++++ estate/views/estate_property_views.xml | 12 ++++++++++++ 4 files changed, 43 insertions(+), 3 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 a185ac3084a..b0851c60909 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,5 +8,8 @@ "license": "LGPL-3", "data": [ 'security/ir.model.access.csv', + + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 25b4fcea520..5c03273dcb2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,3 +1,5 @@ +from dateutil.relativedelta import relativedelta + from odoo import models, fields class EstateProperty(models.Model): @@ -9,6 +11,15 @@ class EstateProperty(models.Model): required = True, ) + active = fields.Boolean( + default = True, + ) + + status = fields.Selection( + [["new", "New"], ["offer_received", "Offer Received"], ["offer_accepted", "Offer Accepted"], ["sold", "Sold"], ["cancelled", "Cancelled"]], + default="new", + ) + description = fields.Text( "Description" ) @@ -18,7 +29,9 @@ class EstateProperty(models.Model): ) date_availability = fields.Date( - "Date Available" + "Date Available", + copy = False, + default = fields.Date.today() + relativedelta(months=+3), ) expected_price = fields.Float( @@ -27,11 +40,14 @@ class EstateProperty(models.Model): ) selling_price = fields.Float( - "Selling Price" + "Selling Price", + copy = False, + readonly = True, ) bedrooms = fields.Integer( - "Bedrooms" + "Bedrooms", + default = 2, ) living_area = fields.Integer( diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..f2ae8ec28c7 --- /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..0b4dd9b690d --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,12 @@ + + + + + Estate Property + estate.property + list,form + + + + + From 1075e8c737d3c74da910ee94479a48a3ede307d8 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Tue, 19 May 2026 17:02:56 +0200 Subject: [PATCH 03/12] [IMP] Init Views for Estate Property model This adds search, list, and form view to the estate property model following server 101 chapter 6. --- estate/models/estate_property.py | 6 +- estate/views/estate_property_views.xml | 80 +++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 5c03273dcb2..75e133840ce 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -25,11 +25,11 @@ class EstateProperty(models.Model): ) postcode = fields.Char( - "Post Code" + "Postcode" ) date_availability = fields.Date( - "Date Available", + "Available From", copy = False, default = fields.Date.today() + relativedelta(months=+3), ) @@ -51,7 +51,7 @@ class EstateProperty(models.Model): ) living_area = fields.Integer( - "Living Area" + "Living Area (sqm)" ) facades = fields.Integer( diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0b4dd9b690d..6862382e729 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,11 +2,89 @@ - Estate Property + Estates List estate.property list,form + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
From 2582c083297d26608edf012362ab324fa19b466b Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Wed, 20 May 2026 11:00:59 +0200 Subject: [PATCH 04/12] [IMP] Estate: Added property offers, tags, and types. Estates can now have multiple offers, tags, and one type. Offers can be made directly on an estate form, and all offers can be viewed via a property offers list menu. --- estate/__manifest__.py | 3 + estate/models/__init__.py | 5 +- estate/models/estate_property.py | 25 +++++++ estate/models/estate_property_offer.py | 32 +++++++++ estate/models/estate_property_tag.py | 15 ++++ estate/models/estate_property_type.py | 16 +++++ estate/security/ir.model.access.csv | 5 +- estate/views/estate_menus.xml | 7 +- estate/views/estate_property_offer_views.xml | 50 +++++++++++++ estate/views/estate_property_tag_views.xml | 23 ++++++ estate/views/estate_property_type_views.xml | 23 ++++++ estate/views/estate_property_views.xml | 74 +++++++++++++------- 12 files changed, 248 insertions(+), 30 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 b0851c60909..f83903a7844 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,9 @@ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_menus.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f2db223b7d4..9e4c8fe647c 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,3 +1,6 @@ # -*- coding: utf-8 -*- -from . import estate_property \ No newline at end of file +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 75e133840ce..a4371798ca4 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -6,6 +6,31 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "technical training estate property model" + estate_property_type_id = fields.Many2one( + "estate.property.type", + ondelete="set null", + ) + + estate_property_tag_ids = fields.Many2many( + "estate.property.tag", + ) + + buyer_id = fields.Many2one( + "res.partner", + ondelete="set null", + ) + + seller_id = fields.Many2one( + "res.users", + ondelete="set null", + default=lambda self: self.env.user.id, + ) + + estate_property_offer_ids = fields.One2many( + "estate.property.offer", + "estate_property_id" + ) + name = fields.Char( "Estate Property", required = True, diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..cbec1b3b449 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,32 @@ +from odoo import models, fields + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "tech training estate property tag" + + estate_property_id = fields.Many2one( + "estate.property", + ondelete="cascade", + required=True, + ) + + buyer_id = fields.Many2one( + "res.partner", + string="Partner", + ondelete="cascade", + default=lambda self: self.env.user.partner_id.id, + required=True, + ) + + price = fields.Float( + string="Price", + required=True, + aggregator="max", + ) + + status = fields.Selection( + [["new", "New"], ["refused", "Refused"], ["accepted", "Accepted"]], + default="new", + copy=False, + ) \ 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..d4ce78ebd5c --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,15 @@ +from odoo import models, fields + + +class EstatePropertyTag(models.Model): + _name = 'estate.property.tag' + _description = 'tech training estate property tag' + + estate_property_ids = fields.Many2many( + "estate.property", + ) + + name = fields.Char( + "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..5fa512980f8 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,16 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "technical training estate property type model" + + estate_property_ids = fields.One2many( + "estate.property", + "estate_property_type_id" + ) + + name = fields.Char( + "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 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 f2ae8ec28c7..e839ec78d06 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,8 +2,13 @@ - + + + + + + \ 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..717eb1123ff --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,50 @@ + + + + + Property Offers + estate.property.offer + list,form + + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + + + estate.property.offer.list + 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..2bd4b96c6e7 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,23 @@ + + + + + Property Tags + estate.property.tag + list,form + + + + + + estate.property.tag.form + estate.property.tag + +
+
+

+
+
+
+
+
\ 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..86534ba0a08 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,23 @@ + + + + + Property Types + estate.property.type + list,form + + + + + + 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 6862382e729..92e88758a2a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,7 +2,7 @@ - Estates List + Properties estate.property list,form @@ -21,6 +21,8 @@ + + @@ -37,6 +39,7 @@ + @@ -54,35 +57,52 @@
- - - - - - +
+

+
+ - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - -
+ + + + + + + + + + + + + + + + + + + + +
From 59cfd6f71b5f5f019811efe4a6d07eb5f3ba937b Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Wed, 20 May 2026 13:45:09 +0200 Subject: [PATCH 05/12] [IMP] Estate: Added computed fields for property offers and properties. Estate form now adds default data when garden is checked, and clears data when unchecked. Estate form now also shows current best offer. Offer form will compute validity days on deadline selection, and also inversely adjust validity days to match final saved deadline on save. --- estate/models/estate_property.py | 36 ++++++++++++++++++-- estate/models/estate_property_offer.py | 21 +++++++++++- estate/views/estate_property_offer_views.xml | 12 +++++-- estate/views/estate_property_views.xml | 2 ++ 4 files changed, 66 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a4371798ca4..14039899508 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,11 +1,12 @@ from dateutil.relativedelta import relativedelta -from odoo import models, fields +from odoo import models, fields, api class EstateProperty(models.Model): _name = "estate.property" _description = "technical training estate property model" + # RELATIONAL FIELDS estate_property_type_id = fields.Many2one( "estate.property.type", ondelete="set null", @@ -31,6 +32,27 @@ class EstateProperty(models.Model): "estate_property_id" ) + # COMPUTED FIELDS + total_area = fields.Integer(compute="_compute_total_area") + @api.depends("living_area") + def _compute_total_area(self): + for estate in self: + null_safe_garden_area = 0 + if estate.garden_area: + null_safe_garden_area = estate.garden_area + + estate.total_area = estate.living_area + null_safe_garden_area + + best_price = fields.Float("Best Offer", compute="_compute_best_price") + @api.depends("estate_property_offer_ids") + def _compute_best_price(self): + for estate in self: + if len(estate.estate_property_offer_ids) > 0: + estate.best_price = max(self.estate_property_offer_ids.mapped("price")) + else: + estate.best_price = 0 + + # BASIC FIELDS name = fields.Char( "Estate Property", required = True, @@ -99,4 +121,14 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( [["north", "North"],["east", "East"], ["south", "South"], ["west", "West"]], - ) \ No newline at end of file + ) + + # ONCHANGE BEHAVIOUR + @api.onchange("garden") + def _onchange_garden_orientation(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = None + self.garden_orientation = None \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index cbec1b3b449..926b0fd3aa1 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,10 +1,13 @@ -from odoo import models, fields +from datetime import timedelta + +from odoo import models, fields, api class EstatePropertyOffer(models.Model): _name = "estate.property.offer" _description = "tech training estate property tag" + # RELATIONAL FIELDS estate_property_id = fields.Many2one( "estate.property", ondelete="cascade", @@ -19,6 +22,22 @@ class EstatePropertyOffer(models.Model): required=True, ) + # COMPUTED FIELDS + validity = fields.Integer(default=7) + date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline") + @api.depends("validity") + def _compute_date_deadline(self): + for estate in self: + if estate.create_date: + estate.date_deadline = estate.create_date + timedelta(days=estate.validity) + else: + estate.date_deadline = fields.Date.today() + timedelta(days=estate.validity) + + def _inverse_date_deadline(self): + for estate in self: + estate.validity = (estate.date_deadline - fields.Date.today()).days + + # BASIC FIELDS price = fields.Float( string="Price", required=True, diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 717eb1123ff..35ce78f968c 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -17,6 +17,8 @@ + +
@@ -27,9 +29,11 @@ estate.property.offer.list estate.property.offer - + + + @@ -40,9 +44,13 @@ estate.property.offer.list estate.property.offer -
+ + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 92e88758a2a..15022d57442 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -71,7 +71,9 @@ + + From d4c20f44be41e9af24e2cc0ad84f89f753f89711 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Wed, 20 May 2026 15:14:47 +0200 Subject: [PATCH 06/12] [IMP] Estate: new sell, cancel, restore actions on estate property, accept and refuse actions on offers --- estate/models/estate_property.py | 27 ++++++++++++++- estate/models/estate_property_offer.py | 36 +++++++++++++++++++- estate/views/estate_property_offer_views.xml | 4 +++ estate/views/estate_property_views.xml | 12 +++++++ 4 files changed, 77 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 14039899508..19a83bb3426 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,8 @@ from dateutil.relativedelta import relativedelta from odoo import models, fields, api +from odoo.exceptions import UserError + class EstateProperty(models.Model): _name = "estate.property" @@ -131,4 +133,27 @@ def _onchange_garden_orientation(self): self.garden_orientation = "north" else: self.garden_area = None - self.garden_orientation = None \ No newline at end of file + self.garden_orientation = None + + # ACTIONS + def action_set_sold(self): + self.ensure_one() # this button should only exist on a form + if not self.active: + raise UserError("cannot set inactive/cancelled estate to sold") + + self.status = "sold" + return True + + def action_set_cancelled(self): + self.ensure_one() # this button should only exist on a form + if self.status == "sold": + raise UserError("cannot sell inactive/cancelled estate") + self.status = "cancelled" + self.active = False + return True + + def action_uncancel(self): + self.ensure_one() + self.status = "new" + self.active = True + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 926b0fd3aa1..785c235eaa2 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from datetime import timedelta +import odoo.exceptions from odoo import models, fields, api @@ -35,6 +36,9 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for estate in self: + new_validity = (estate.date_deadline - fields.Date.today()).days + if new_validity < 0: + raise odoo.exceptions.UserError("validity < 0") estate.validity = (estate.date_deadline - fields.Date.today()).days # BASIC FIELDS @@ -48,4 +52,34 @@ def _inverse_date_deadline(self): [["new", "New"], ["refused", "Refused"], ["accepted", "Accepted"]], default="new", copy=False, - ) \ No newline at end of file + ) + + # ACTIONS + def action_accept_offer(self): + self.ensure_one() + for offer in self: + if fields.Date.today() > offer.date_deadline: + raise odoo.exceptions.UserError("offer expired") + + estate = offer.estate_property_id + for recur_offer in estate.estate_property_offer_ids: + if recur_offer.status == "accepted": + # more foolproof than just checking estate_property.status + raise odoo.exceptions.UserError("another offer already accepted") + + estate.buyer_id = offer.buyer_id + estate.selling_price = offer.price + estate.status = "offer_accepted" + + offer.status = "accepted" + return True + + def action_refuse_offer(self): + self.ensure_one() + self.status = "refused" + + estate = self.estate_property_id + estate.buyer_id = None + estate.selling_price = None + estate.status = "offer_received" + 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 35ce78f968c..e3e8e377cc3 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -35,6 +35,10 @@ + +

+ + + + + + +
+ + + estate.property.type.list + 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 42af20cc36b..9ea45f1a97d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,6 +5,10 @@ Properties estate.property list,form + + + {'search_default_filter_available_status': 1 } + @@ -18,15 +22,15 @@ - + - - + - + @@ -38,15 +42,20 @@ estate.property.list estate.property - - - - - - - - - + + + + + + + + + + @@ -58,31 +67,35 @@
-
+
+ invisible="status != 'cancelled'"/> +
+ + + -
-

-
+ +
+

+
+
- - + - + @@ -93,28 +106,37 @@ - + s - - - - - - - - + + + + + + + + + + + + + + - + + + From c456beb4447eed9b2785e7cc181131119b9f13d7 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Thu, 21 May 2026 14:53:11 +0200 Subject: [PATCH 09/12] [IMP] Estate: implemented inheritance with res_user Creating offers will set property to offer_received, and offers lower than current minimum can no longer be created. available properties can be accessed through their seller's user profile --- estate/__manifest__.py | 1 + estate/models/__init__.py | 3 ++- estate/models/estate_property.py | 11 ++++++++ estate/models/estate_property_offer.py | 37 ++++++++++++++++++++++++++ estate/models/res_users.py | 10 +++++++ estate/views/res_users_views.xml | 18 +++++++++++++ estate_account/__init__.py | 0 estate_account/__manifest__.py | 8 ++++++ 8 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_views.xml create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ffd22f87acb..b919a9e6aeb 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,5 +14,6 @@ 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_menus.xml', + 'views/res_users_views.xml' ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 9e4c8fe647c..89af74d7e7f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,4 +3,5 @@ from . import estate_property from . import estate_property_type from . import estate_property_tag -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer +from . import res_users \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 220c3b6b061..faf2a26e4a0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -10,6 +10,17 @@ class EstateProperty(models.Model): _description = "technical training estate property model" _order = "id desc" + # METHODS + @api.ondelete(at_uninstall=False) + def _unlink_if_new_cancelled(self): + for user in self: + if user.status not in ("new", "cancelled"): + raise UserError("can't delete active estate property listing") + + def set_offer_received(self): + for estate in self: + estate.status = "offer_received" + # SQL CONSTRAINTS _check_expected_price_positive = models.Constraint( "CHECK(expected_price > 0)", diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e81d7750afb..c6e8cd7b13b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -14,6 +14,43 @@ class EstatePropertyOffer(models.Model): "Offer price must be greater than 0" ) + # INHERITED METHODS + @api.model + def create(self, vals): + for new_offer in vals: + current_property_offers_price_asc = self.env["estate.property.offer"].search( + [('estate_property_id', '=', new_offer.get("estate_property_id"))] + ).sorted("price asc") + + property = self.env["estate.property"].browse(new_offer.get("estate_property_id")) + if not current_property_offers_price_asc or len(current_property_offers_price_asc) == 0: + if property.status == "new": + property.set_offer_received() + return super().create(vals) + + if new_offer.get("price") < current_property_offers_price_asc[0].price: + raise UserError("submitted offer < lowest current offer") + + if property.status == "new": + property.set_offer_received() + return super().create(vals) + + @api.model + def write(self, vals): + updated_price = vals.get("price") + if not updated_price: + return super().write(vals) # just skip, since logic only applies if there's updated price + + current_property_offers_price_asc = self.estate_property_id.estate_property_offer_ids.sorted("price asc") + + if self == current_property_offers_price_asc[0]: + return super().write(vals) + + if updated_price < current_property_offers_price_asc[0].price: + raise UserError("submitted offer < lowest current offer") + + return super().write(vals) + # RELATIONAL FIELDS estate_property_id = fields.Many2one( "estate.property", diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..30a86c0f6e5 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,10 @@ +from odoo import api, fields, models + +class InheritedResUser(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + "estate.property", + "seller_id", + domain=[('status','in',['new', 'offer_received'])], + ) \ No newline at end of file diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..8ca1ed2c6f3 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,18 @@ + + + + + res.users.view.form.inherit.estate.property + res.users + + + + + + + + + + + + \ No newline at end of file diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..85e59493318 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Estate Accounting', + 'depends': ['estate', 'account'], + 'installable': True, + "author": "daloe", + "license": "LGPL-3", +} \ No newline at end of file From fdf673639f661775c41ce725325c8a28885283b9 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Thu, 21 May 2026 15:29:45 +0200 Subject: [PATCH 10/12] [IMP] Estate Account: Create a blank invoice when marking a property as sold, create an invoice when accepting an offer --- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 3 ++ estate_account/models/__init__.py | 2 + estate_account/models/estate_property.py | 34 +++++++++++++++++ .../models/estate_property_offer.py | 37 +++++++++++++++++++ estate_account/security/ir.model.access.csv | 2 + 6 files changed, 79 insertions(+) create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py create mode 100644 estate_account/models/estate_property_offer.py create mode 100644 estate_account/security/ir.model.access.csv diff --git a/estate_account/__init__.py b/estate_account/__init__.py index e69de29bb2d..9a7e03eded3 100644 --- a/estate_account/__init__.py +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 85e59493318..b53d3fdd800 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -5,4 +5,7 @@ 'installable': True, "author": "daloe", "license": "LGPL-3", + "data": [ + 'security/ir.model.access.csv', + ], } \ 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..9c1cb4a3ffa --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1,2 @@ +from . import estate_property +from . import estate_property_offer \ No newline at end of file diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..364859012cb --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,34 @@ +from odoo import models +from odoo.exceptions import AccessError +from odoo.orm.commands import Command + + +class EstateProperty(models.Model): + _inherit = ["estate.property"] + + def action_set_sold(self): + for property in self: + if not self.env['account.move'].has_access('create'): + try: + self.check_access('write') + except AccessError: + return self.env['account.move'] + + self.env['account.move'].create({ + "partner_id": property.buyer_id.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create({ + "name": "6% selling price", + "quantity": 1, + "price_unit": property.selling_price * 0.06 + }), + Command.create({ + "name": "admin fees", + "quantity": 1, + "price_unit": 100000 + }) + ] + }) + + return super().action_set_sold() \ No newline at end of file diff --git a/estate_account/models/estate_property_offer.py b/estate_account/models/estate_property_offer.py new file mode 100644 index 00000000000..4d6d123d005 --- /dev/null +++ b/estate_account/models/estate_property_offer.py @@ -0,0 +1,37 @@ +from odoo import models +from odoo.exceptions import AccessError +from odoo.orm.commands import Command + +class EstatePropertyOffer(models.Model): + _inherit = ["estate.property.offer"] + + def action_accept_offer(self): + for offer in self: + if not self.env['account.move'].has_access('create'): + try: + self.check_access('write') + except AccessError: + return self.env['account.move'] + + self.env['account.move'].create({ + "partner_id": offer.buyer_id.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create({ + "name": offer.estate_property_id.name, + "quantity": 1, + "price_unit": offer.price + }), + Command.create({ + "name": "6%", + "quantity": 1, + "price_unit": offer.price * 0.06 + }), + Command.create({ + "name": "admin fees", + "quantity": 1, + "price_unit": 100000 + }) + ] + }) + return super().action_accept_offer() \ No newline at end of file diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /dev/null +++ b/estate_account/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From 96bd4990d617c223c97563326b248cf4a034f6ce Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Thu, 21 May 2026 16:29:35 +0200 Subject: [PATCH 11/12] [IMP] Estate: Implemented basic kanban view for estate properties --- estate/views/estate_property_views.xml | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9ea45f1a97d..7e4ea7f3fda 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_filter_available_status': 1 } @@ -142,5 +142,36 @@ + + + + estate.property.kanban + estate.property + + + + + + + + + + + + + + + + + + + + + + + From c4ad35f229da1c0e77095a396b20dbb37246d6e0 Mon Sep 17 00:00:00 2001 From: Dani Loewito Date: Fri, 22 May 2026 08:57:33 +0200 Subject: [PATCH 12/12] [IMP] Estate: Edit code to follow coding guidelines --- estate/__init__.py | 4 +- estate/__manifest__.py | 1 - estate/models/__init__.py | 2 +- estate/models/estate_property.py | 115 ++++++++++--------- estate/models/estate_property_offer.py | 111 +++++++++--------- estate/models/estate_property_tag.py | 18 +-- estate/models/estate_property_type.py | 36 +++--- estate/models/res_users.py | 2 +- estate/views/estate_menus.xml | 6 +- estate/views/estate_property_offer_views.xml | 28 ++--- estate/views/estate_property_type_views.xml | 2 +- estate/views/estate_property_views.xml | 38 +++--- estate/views/res_users_views.xml | 3 - 13 files changed, 183 insertions(+), 183 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 5305644df14..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1,3 +1 @@ -# -*- coding: utf-8 -*- - -from . import models \ No newline at end of file +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b919a9e6aeb..ea570d71dab 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- { 'name': "Estate", 'depends': ['base'], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 89af74d7e7f..c2df8d540dd 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -4,4 +4,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer -from . import res_users \ No newline at end of file +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index faf2a26e4a0..b65957249b2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,45 +1,14 @@ from dateutil.relativedelta import relativedelta -from odoo import models, fields, api +from odoo import api, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools import float_compare - class EstateProperty(models.Model): _name = "estate.property" _description = "technical training estate property model" _order = "id desc" - # METHODS - @api.ondelete(at_uninstall=False) - def _unlink_if_new_cancelled(self): - for user in self: - if user.status not in ("new", "cancelled"): - raise UserError("can't delete active estate property listing") - - def set_offer_received(self): - for estate in self: - estate.status = "offer_received" - - # SQL CONSTRAINTS - _check_expected_price_positive = models.Constraint( - "CHECK(expected_price > 0)", - "Please hold at least a little hope of selling this" - ) - - _check_selling_price_positive = models.Constraint( - "CHECK(selling_price is null or selling_price > 0)", - "Selling price must be greater than 0" - ) - - #PYTHON CONSTRAINTS - @api.constrains('selling_price') - def _check_ninety_percent_of_expected(self): - for estate in self: - if estate.selling_price > 0: # restrict checks to only when offer acceptance happens - if float_compare(estate.selling_price, 0.9 * estate.expected_price, 2) < 0: - raise ValidationError("Offer is <90% of expected") - # RELATIONAL FIELDS estate_property_type_id = fields.Many2one( "estate.property.type", @@ -67,26 +36,6 @@ def _check_ninety_percent_of_expected(self): copy=False, ) - # COMPUTED FIELDS - total_area = fields.Integer(compute="_compute_total_area") - @api.depends("living_area") - def _compute_total_area(self): - for estate in self: - null_safe_garden_area = 0 - if estate.garden_area: - null_safe_garden_area = estate.garden_area - - estate.total_area = estate.living_area + null_safe_garden_area - - best_price = fields.Float("Best Offer", compute="_compute_best_price") - @api.depends("estate_property_offer_ids") - def _compute_best_price(self): - for estate in self: - if len(estate.estate_property_offer_ids) > 0: - estate.best_price = max(self.estate_property_offer_ids.mapped("price")) - else: - estate.best_price = 0 - # BASIC FIELDS name = fields.Char( "Estate Property", @@ -158,7 +107,48 @@ def _compute_best_price(self): [["north", "North"],["east", "East"], ["south", "South"], ["west", "West"]], ) - # ONCHANGE BEHAVIOUR + # COMPUTED FIELDS + total_area = fields.Integer(compute="_compute_total_area") + + best_price = fields.Float("Best Offer", compute="_compute_best_price") + + # SQL CONSTRAINTS & INDEXES + _check_expected_price_positive = models.Constraint( + "CHECK(expected_price > 0)", + "expected_price <= 0" + ) + + _check_selling_price_positive = models.Constraint( + "CHECK(selling_price is null or selling_price > 0)", + "selling_price <= 0" + ) + + # COMPUTE & INVERSE & SEARCH + @api.depends("living_area") + def _compute_total_area(self): + for estate in self: + null_safe_garden_area = 0 + if estate.garden_area: + null_safe_garden_area = estate.garden_area + + estate.total_area = estate.living_area + null_safe_garden_area + + @api.depends("estate_property_offer_ids") + def _compute_best_price(self): + for estate in self: + if len(estate.estate_property_offer_ids) > 0: + estate.best_price = max(self.estate_property_offer_ids.mapped("price")) + else: + estate.best_price = 0 + + # PYTHON CONSTRAINS & ONCHANGE + @api.constrains('selling_price') + def _check_ninety_percent_of_expected(self): + for estate in self: + if estate.selling_price > 0: # restrict checks to only when offer acceptance happens + if float_compare(estate.selling_price, 0.9 * estate.expected_price, 2) < 0: + raise ValidationError("Offer is <90% of expected") + @api.onchange("garden") def _onchange_garden_orientation(self): if self.garden: @@ -168,11 +158,20 @@ def _onchange_garden_orientation(self): self.garden_area = None self.garden_orientation = None + # ORM OVERRIDES + @api.ondelete(at_uninstall=False) + def _unlink_if_new_cancelled(self): + for user in self: + if user.status not in ("new", "cancelled"): + error = self.env._("Cannot delete active listing") + raise UserError(error) + # ACTIONS def action_set_sold(self): self.ensure_one() # this button should only exist on a form if not self.active: - raise UserError("cannot set inactive/cancelled estate to sold") + error = self.env._("Cannot set inactive/cancelled listing to sold") + raise UserError(error) self.status = "sold" return True @@ -180,7 +179,8 @@ def action_set_sold(self): def action_set_cancelled(self): self.ensure_one() # this button should only exist on a form if self.status == "sold": - raise UserError("cannot sell inactive/cancelled estate") + error = self.env._("Cannot set sold listing to cancelled") + raise UserError(error) self.status = "cancelled" return True @@ -188,3 +188,8 @@ def action_uncancel(self): self.ensure_one() self.status = "new" return True + + # BUSINESS LOGIC + def set_offer_received(self): + for estate in self: + estate.status = "offer_received" \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index c6e8cd7b13b..aefff74536d 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -8,49 +8,6 @@ class EstatePropertyOffer(models.Model): _description = "tech training estate property tag" _order = "price desc" - # SQL CONSTRAINTS - _check_positive_offer = models.Constraint( - "CHECK(price > 0)", - "Offer price must be greater than 0" - ) - - # INHERITED METHODS - @api.model - def create(self, vals): - for new_offer in vals: - current_property_offers_price_asc = self.env["estate.property.offer"].search( - [('estate_property_id', '=', new_offer.get("estate_property_id"))] - ).sorted("price asc") - - property = self.env["estate.property"].browse(new_offer.get("estate_property_id")) - if not current_property_offers_price_asc or len(current_property_offers_price_asc) == 0: - if property.status == "new": - property.set_offer_received() - return super().create(vals) - - if new_offer.get("price") < current_property_offers_price_asc[0].price: - raise UserError("submitted offer < lowest current offer") - - if property.status == "new": - property.set_offer_received() - return super().create(vals) - - @api.model - def write(self, vals): - updated_price = vals.get("price") - if not updated_price: - return super().write(vals) # just skip, since logic only applies if there's updated price - - current_property_offers_price_asc = self.estate_property_id.estate_property_offer_ids.sorted("price asc") - - if self == current_property_offers_price_asc[0]: - return super().write(vals) - - if updated_price < current_property_offers_price_asc[0].price: - raise UserError("submitted offer < lowest current offer") - - return super().write(vals) - # RELATIONAL FIELDS estate_property_id = fields.Many2one( "estate.property", @@ -71,9 +28,31 @@ def write(self, vals): required=True, ) + # BASIC FIELDS + price = fields.Float( + string="Price", + required=True, + aggregator="max", + ) + + status = fields.Selection( + [["new", "New"], ["refused", "Refused"], ["accepted", "Accepted"]], + default="new", + copy=False, + ) + # COMPUTED FIELDS validity = fields.Integer(default=7) + date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + # SQL CONSTRAINTS & INDEXES + _check_positive_offer = models.Constraint( + "CHECK(price > 0)", + "Offer price must be greater than 0" + ) + + # COMPUTE & INVERSE & SEARCH @api.depends("validity") def _compute_date_deadline(self): for estate in self: @@ -89,18 +68,42 @@ def _inverse_date_deadline(self): raise UserError("validity < 0") estate.validity = (estate.date_deadline - fields.Date.today()).days - # BASIC FIELDS - price = fields.Float( - string="Price", - required=True, - aggregator="max", - ) + # ORM OVERRIDES + @api.model + def create(self, vals): + for new_offer in vals: + current_property_offers_price_asc = self.env["estate.property.offer"].search( + [('estate_property_id', '=', new_offer.get("estate_property_id"))] + ).sorted("price asc") - status = fields.Selection( - [["new", "New"], ["refused", "Refused"], ["accepted", "Accepted"]], - default="new", - copy=False, - ) + estate_property = self.env["estate.property"].browse(new_offer.get("estate_property_id")) + if not current_property_offers_price_asc or len(current_property_offers_price_asc) == 0: + if estate_property.status == "new": + estate_property.set_offer_received() + return super().create(vals) + + if new_offer.get("price") < current_property_offers_price_asc[0].price: + raise UserError("submitted offer < lowest current offer") + + if estate_property.status == "new": + estate_property.set_offer_received() + return super().create(vals) + + @api.model + def write(self, vals): + updated_price = vals.get("price") + if not updated_price: + return super().write(vals) # just skip, since logic only applies if there's updated price + + current_property_offers_price_asc = self.estate_property_id.estate_property_offer_ids.sorted("price asc") + + if self == current_property_offers_price_asc[0]: + return super().write(vals) + + if updated_price < current_property_offers_price_asc[0].price: + raise UserError("submitted offer < lowest current offer") + + return super().write(vals) # ACTIONS def action_accept_offer(self): diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 917941144dd..9dd61f57671 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,20 +1,16 @@ -from odoo import models, fields, api - +from odoo import models, fields class EstatePropertyTag(models.Model): _name = 'estate.property.tag' _description = 'tech training estate property tag' _order = 'name asc' - _check_unique_name = models.UniqueIndex( - "(UPPER(name))", - "Tag should be unique" - ) - + # RELATIONAL FIELDS estate_property_ids = fields.Many2many( "estate.property", ) + # BASIC FIELDS name = fields.Char( "Tag", required=True, @@ -23,4 +19,10 @@ class EstatePropertyTag(models.Model): color = fields.Integer( string="Color", default=0, - ) \ No newline at end of file + ) + + # SQL CONSTRAINTS & INDEXES + _check_unique_name = models.UniqueIndex( + "(UPPER(name))", + "Tag should be unique" + ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index a253faf982c..b5111d971f3 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,16 +1,10 @@ from odoo import models, fields, api - class EstatePropertyType(models.Model): _name = "estate.property.type" _description = "technical training estate property type model" _order = "name asc" - _check_unique_name = models.UniqueIndex( - "(UPPER(name))", - "Type should be unique" - ) - # RELATIONAL FIELDS estate_property_ids = fields.One2many( "estate.property", @@ -22,10 +16,29 @@ class EstatePropertyType(models.Model): "property_type_id" ) + # BASIC FIELDS + name = fields.Char( + "Type", + required=True, + ) + + sequence = fields.Integer( + 'Sequence', + default=1, + ) + # COMPUTED FIELDS offer_count = fields.Integer( compute="_compute_offer_count", ) + + # SQL CONSTRAINTS & INDEXES + _check_unique_name = models.UniqueIndex( + "(UPPER(name))", + "Type should be unique" + ) + + # COMPUTE & INVERSE & SEARCH @api.depends('estate_property_offer_ids') def _compute_offer_count(self): for prop_type in self: @@ -33,14 +46,3 @@ def _compute_offer_count(self): prop_type.offer_count = len(prop_type.estate_property_offer_ids) else: prop_type.offer_count = 0 - - # BASIC FIELDS - name = fields.Char( - "Type", - required=True, - ) - - sequence = fields.Integer( - 'Sequence', - default=1, - ) \ No newline at end of file diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 30a86c0f6e5..8be97a511c9 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import fields, models class InheritedResUser(models.Model): _inherit = "res.users" diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 8ce495ecbc3..e62dd2790c0 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,11 +2,11 @@ - + - + - + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 483b938c851..eb11b19075d 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,13 +1,13 @@ - + Property Offers estate.property.offer list,form - + Property Offers estate.property.offer list,form @@ -24,8 +24,8 @@ - - + + @@ -35,18 +35,18 @@ - + estate.property.offer.related.list estate.property.offer - - - - - + + + + +