From 8424d455c6c78bc5770898315f4f10e64e993faf Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 11:08:18 +0100 Subject: [PATCH 01/19] [ADD] estate: initialize estate module initial structure of the new estate module --- estate/__init__.py | 0 estate/__manifest__.py | 8 ++++++++ 2 files changed, 8 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..ecead5b1a2d --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,8 @@ +{ + 'name': 'estate', + 'depends': [ + 'base' + ], + 'application': True, + 'installable': True +} \ No newline at end of file From 3451b714e0ed11d77cfc71d727b1a2b6edf9a997 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 11:36:50 +0100 Subject: [PATCH 02/19] [IMP] generic: add ruff configs adding config file and a script to run before commits to check formatting --- ruff.toml | 83 +++++++++++++++++++++++++++++++++++++++++++++++++ scripts/ruff.sh | 1 + 2 files changed, 84 insertions(+) create mode 100644 ruff.toml create mode 100644 scripts/ruff.sh diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 00000000000..543fa0290c0 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,83 @@ +# automatically generated file by the runbot nightly ruff checks, do not modify +# for ruff version 0.11.4 (or higher) +# note: 'E241', 'E272', 'E201', 'E221' are ignored on runbot in test files when formating a table like structure (more than two space) +# some rules present here are not enabled on runbot (yet) but are still advised to follow when possible : ["B904", "COM812", "E741", "EM101", "I001", "RET", "RUF021", "TRY002", "UP006", "UP007"] + + +target-version = "py310" + +[lint] +preview = true +select = [ + "BLE", # flake8-blind-except + "C", # flake8-comprehensions + "COM", # flake8-commas + "E", # pycodestyle Error + "EM", # flake8-errmsg + "EXE", # flake8-executable + "F", # Pyflakes + "FA", # flake8-future-annotations + "FLY", # flynt + "G", # flake8-logging-format + "I", # isort + "ICN", # flake8-import-conventions + "INT", # flake8-gettext + "ISC", # flake8-implicit-str-concat + "LOG", # flake8-logging + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PLC", # Pylint Convention + "PLE", # Pylint Error + "PLW", # Pylint Warning + "PYI", # flake8-pyi + "RET", # flake8-return + "RUF", # Ruff-specific rules + "SIM", # flake8-simplify + "SLOT", # flake8-slots + "T", # flake8-print + "TC", # flake8-type-checking + "TID", # flake8-tidy-imports + "TRY", # tryceratops + "UP", # pyupgrade + "W", # pycodestyle Warning + "YTT", # flake8-2020 +] +ignore = [ + "C408", # unnecessary-collection-call + "C420", # unnecessary-dict-comprehension-for-iterable + "C901", # complex-structure + "E266", # multiple-leading-hashes-for-block-comment + "E501", # line-too-long + "E713", # not-in-test + "EM102", # f-string-in-exception + "FA100", # future-rewritable-type-annotation + "PGH003", # blanket-type-ignore + "PIE790", # unnecessary-placeholder + "PIE808", # unnecessary-range-start + "PLC2701", # import-private-name + "PLW2901", # redefined-loop-name + "RUF001", # ambiguous-unicode-character-string + "RUF005", # collection-literal-concatenation + "RUF012", # mutable-class-default + "RUF100", # unused-noqa + "SIM102", # collapsible-if + "SIM108", # if-else-block-instead-of-if-exp + "SIM117", # multiple-with-statements + "TID252", # relative-imports + "TRY003", # raise-vanilla-args + "TRY300", # try-consider-else + "TRY400", # error-instead-of-exception + "UP031", # printf-string-formatting + "UP038", # non-pep604-isinstance +] + +[lint.per-file-ignores] +"**/__init__.py" = [ + "F401", # unused-import +] + +[lint.isort] +# https://www.odoo.com/documentation/master/contributing/development/coding_guidelines.html#imports +section-order = ["future", "standard-library", "third-party", "first-party", "local-folder"] +known-first-party = ["odoo"] +known-local-folder = ["odoo.addons"] diff --git a/scripts/ruff.sh b/scripts/ruff.sh new file mode 100644 index 00000000000..76464bf6af0 --- /dev/null +++ b/scripts/ruff.sh @@ -0,0 +1 @@ +ruff check ./estate --fix && ruff format ./estate \ No newline at end of file From 1c06c665f3f9ae68f6a4b40408c5d7520db3d5c0 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 11:40:34 +0100 Subject: [PATCH 03/19] [LINT] estate: apply ruff linting on __manifest__.py --- estate/__manifest__.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index ecead5b1a2d..329265ed8ea 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,8 +1,8 @@ { - 'name': 'estate', - 'depends': [ - 'base' + "name": "estate", + "depends": [ + "base", ], - 'application': True, - 'installable': True -} \ No newline at end of file + "application": True, + "installable": True, +} From 252aa2846c9e8f1cff76ab6313ccd053a32184f0 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 14:16:17 +0100 Subject: [PATCH 04/19] [IMP] estate: create EstateProperty model Adding definition of estate_property model along with its attributes --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 27 +++++++++++++++++++++++++++ 3 files changed, 29 insertions(+) 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 index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..91b4b6abc46 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,27 @@ +from odoo import fields, models + + +class EstateProperty(models.Model): + _name = "estate_property" + _description = "Real state property" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ] + ) From 969410cbb83385a1d20c4ac70c4ae197183cf772 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 14:22:29 +0100 Subject: [PATCH 05/19] [FIX] add manifest missing fields Adding author and license to fix GitHub CI/tutorials action --- estate/__manifest__.py | 2 ++ estate/models/estate_property.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 329265ed8ea..107350a99d6 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,4 +5,6 @@ ], "application": True, "installable": True, + "author": "Gendi (HOELG)", + "license": "LGPL-3", } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 91b4b6abc46..e1d637ca8b6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -23,5 +23,5 @@ class EstateProperty(models.Model): ("south", "South"), ("east", "East"), ("west", "West"), - ] + ], ) From 58c8f38682d1995ed748243d31d1c5c1cc8d9ae5 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 15:19:37 +0100 Subject: [PATCH 06/19] [IMP] estate: add access for estate_property Adding read access right for base.group_user --- 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 107350a99d6..fdf8cad73c3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,6 +3,9 @@ "depends": [ "base", ], + "data": [ + "security/ir.model.access.csv", + ], "application": True, "installable": True, "author": "Gendi (HOELG)", diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..9ded16928e3 --- /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,0,0,0 \ No newline at end of file From 9274576140dfd60dd958b298698d575487cc6b01 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 16:48:53 +0100 Subject: [PATCH 07/19] [IMP] estate: follow model naming convention rename model to use . instead of _ to follow naming convention --- estate/models/estate_property.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e1d637ca8b6..591f55ad60f 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,7 +2,7 @@ class EstateProperty(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "Real state property" name = fields.Char(required=True) From cb5c04bee1e0967a3a0dd63a5b688f119cb511b0 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Tue, 20 Jan 2026 17:15:31 +0100 Subject: [PATCH 08/19] [IMP] estate: grant access rights allow other access patterns on estate.property to be able to create a property --- estate/security/ir.model.access.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 9ded16928e3..0e11f47e58d 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_estate_property,access_estate_property,model_estate_property,base.group_user,1,0,0,0 \ No newline at end of file +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From ca6ae7397f8e640bb87e99251db5788c4c00dc6a Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Wed, 21 Jan 2026 10:59:14 +0100 Subject: [PATCH 09/19] [IMP] estate: add the estate.property form view Adding the form view with basic functionality for estate.property along with menus with the single resource we have so far. --- estate/__manifest__.py | 2 ++ estate/models/estate_property.py | 22 +++++++++++++++++++--- estate/views/estate_menus.xml | 8 ++++++++ estate/views/estate_property_views.xml | 8 ++++++++ 4 files changed, 37 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 fdf8cad73c3..9b4725a8775 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,8 @@ ], "data": [ "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", ], "application": True, "installable": True, diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 591f55ad60f..66e83450efd 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 fields, models @@ -8,10 +10,13 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date( + copy=False, + default=fields.Date.today() + relativedelta(months=3), + ) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -25,3 +30,14 @@ class EstateProperty(models.Model): ("west", "West"), ], ) + state = fields.Selection( + selection=[ + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + default="new", + ) + active = fields.Boolean(default=True) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..479a1658a01 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + 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 566bddafbb4bf6b6d3d1e8192be165ca3709dda9 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Wed, 21 Jan 2026 11:31:03 +0100 Subject: [PATCH 10/19] [LINT] adding new lines at end of files following convention of having a new at the end of every file --- estate/security/ir.model.access.csv | 2 +- estate/views/estate_property_views.xml | 2 +- scripts/ruff.sh | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..32389642d4f 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_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 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index affda3b8abc..1d2a3aaa4cd 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,4 +5,4 @@ estate.property list,form - \ No newline at end of file + diff --git a/scripts/ruff.sh b/scripts/ruff.sh index 76464bf6af0..e45d20cd59c 100644 --- a/scripts/ruff.sh +++ b/scripts/ruff.sh @@ -1 +1 @@ -ruff check ./estate --fix && ruff format ./estate \ No newline at end of file +ruff check ./estate --fix && ruff format ./estate From a6fb90447c0f342ebc4debad096a66b17509a290 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Wed, 21 Jan 2026 17:27:42 +0100 Subject: [PATCH 11/19] [IMP] estate: implement basic views Adding basic (form, list, search) views for the propety model along with some UI customizations --- estate/models/estate_property.py | 7 ++- estate/views/estate_property_views.xml | 80 ++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 66e83450efd..cefdfaf7db3 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -7,21 +7,22 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Real state property" - name = fields.Char(required=True) + name = fields.Char(required=True, string="Title") description = fields.Text() postcode = fields.Char() date_availability = fields.Date( copy=False, default=fields.Date.today() + relativedelta(months=3), + string="Available From", ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) - living_area = fields.Integer() + living_area = fields.Integer(string="Living Area (sqm)") facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() + garden_area = fields.Integer(string="Garden Area (sqm)") garden_orientation = fields.Selection( selection=[ ("north", "North"), diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1d2a3aaa4cd..1922f11078d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,85 @@ + + estate.property.view.search + estate.property + + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + +

+ +

+ + + + + + + + + + +
+ + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.list + estate.property + + + + + + + + + + + + + Properties estate.property From 54b4659f9b66436cb06cad83f4a062b489f3d03c Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Thu, 22 Jan 2026 17:46:53 +0100 Subject: [PATCH 12/19] [IMP] estate: introduce new models for Property with relations Adding new models (property_type, property_tag, property_offer) with relations to the property model and implenting needed views. --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 7 ++++- estate/models/estate_property.py | 22 ++++++++++++++ estate/models/estate_property_offer.py | 23 +++++++++++++++ estate/models/estate_property_tag.py | 8 ++++++ estate/models/estate_property_type.py | 8 ++++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menus.xml | 6 +++- 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 | 14 +++++++++ 12 files changed, 166 insertions(+), 2 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 9b4725a8775..645417bac8e 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,9 @@ ], "data": [ "security/ir.model.access.csv", + "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 5e1963c9d2f..3683ff97b61 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,6 @@ -from . import estate_property +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 cefdfaf7db3..daba980ab93 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,6 +9,10 @@ class EstateProperty(models.Model): name = fields.Char(required=True, string="Title") description = fields.Text() + property_type_id = fields.Many2one( + comodel_name="estate.property.type", + string="Type", + ) postcode = fields.Char() date_availability = fields.Date( copy=False, @@ -42,3 +46,21 @@ class EstateProperty(models.Model): default="new", ) active = fields.Boolean(default=True) + seller = fields.Many2one( + comodel_name="res.users", + string="Salesman", + default=(lambda self: self.env.user), + ) + buyer = fields.Many2one( + comodel_name="res.partner", + copy=False, + ) + tag_ids = fields.Many2many( + comodel_name="estate.property.tag", + string="Tags", + ) + offer_ids = fields.One2many( + comodel_name="estate.property.offer", + inverse_name="property_id", + string="Offers", + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..132b8a3bcb2 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,23 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "an amount a potential buyer offers to the seller" + + price = fields.Float() + status = fields.Selection( + selection=[ + ("accepted", "Accepted"), + ("refused", "Refused"), + ], + copy=False, + ) + partner_id = fields.Many2one( + comodel_name="res.partner", + required=True, + ) + property_id = fields.Many2one( + comodel_name="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..2a3342c5738 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class PropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Tag to be applied to a property" + + 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..f98c15ab8eb --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Type of a real estate property" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 32389642d4f..89f97c50842 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 +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 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 479a1658a01..751e9ad96e0 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,8 +1,12 @@ - + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..f74a717b48b --- /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 + + + + + + + + +
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..21e835ac5bd --- /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 + +
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..6879b9b68e9 --- /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 + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1922f11078d..5d7ba8fdda1 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,6 +6,7 @@ + @@ -36,6 +37,8 @@ + + @@ -58,6 +61,15 @@ + + + + + + + + + @@ -70,6 +82,8 @@ + + From 8373d8e102c685fb81a8659576cca08f02de10c8 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Mon, 26 Jan 2026 09:59:14 +0100 Subject: [PATCH 13/19] [IMP] estate: introduce computed fields for offer and property Adding a bunch of computed fields to show useful collective info about relative models in the form and list views --- estate/models/estate_property.py | 31 +++++++++++++++++++- estate/models/estate_property_offer.py | 23 ++++++++++++++- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index daba980ab93..83a7a381a43 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,10 @@ +import logging + from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models + +logger = logging.getLogger(__name__) class EstateProperty(models.Model): @@ -35,6 +39,9 @@ class EstateProperty(models.Model): ("west", "West"), ], ) + total_area = fields.Integer( + compute="_compute_total_area", string="Total Area (sqm)" + ) state = fields.Selection( selection=[ ("new", "New"), @@ -64,3 +71,25 @@ class EstateProperty(models.Model): inverse_name="property_id", string="Offers", ) + best_offer = fields.Float(compute="_compute_best_offer") + + @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") + def _compute_best_offer(self): + for record in self: + record.best_offer = ( + max(record.offer_ids.mapped("price")) if record.offer_ids 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 = None diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 132b8a3bcb2..f3cc3946528 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from datetime import timedelta + +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -21,3 +23,22 @@ class EstatePropertyOffer(models.Model): comodel_name="estate.property", required=True, ) + validity = fields.Integer(default=7) + date_deadline = fields.Date( + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + string="Deadline", + ) + + @api.depends("validity", "create_date") + def _compute_date_deadline(self): + for record in self: + record.date_deadline = ( + record.create_date or fields.Date.today() + ) + timedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + record.validity = ( + record.date_deadline - fields.Date.to_date(record.create_date) + ).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index f74a717b48b..c53a97f4978 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,6 +9,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5d7ba8fdda1..7679aba403e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -44,6 +44,7 @@ + @@ -59,6 +60,7 @@ + From 03e3412b9f28284d135506ef0ed4455b0f980c33 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Mon, 26 Jan 2026 10:47:21 +0100 Subject: [PATCH 14/19] [IMP] fix field formatting and include property state in views --- estate/views/estate_property_views.xml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7679aba403e..0a598312794 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -12,12 +12,7 @@ - + @@ -56,6 +51,7 @@ + @@ -91,6 +87,7 @@ + From 67fc2fa501b271b5053eee0bd7996e308d2620c3 Mon Sep 17 00:00:00 2001 From: "Gendi (HOELG)" Date: Mon, 26 Jan 2026 11:57:04 +0100 Subject: [PATCH 15/19] [IMP] estate: add actions to property and offer Providing button action on the property form to cancel/sell a property, and to accept/refuse an offer --- estate/models/estate_property.py | 15 ++++++++++++++- estate/models/estate_property_offer.py | 16 ++++++++++++++++ estate/views/estate_property_offer_views.xml | 4 ++++ estate/views/estate_property_views.xml | 4 ++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 83a7a381a43..5985546d2bf 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -3,6 +3,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models +from odoo.exceptions import UserError logger = logging.getLogger(__name__) @@ -40,7 +41,7 @@ class EstateProperty(models.Model): ], ) total_area = fields.Integer( - compute="_compute_total_area", string="Total Area (sqm)" + compute="_compute_total_area", string="Total Area (sqm)", ) state = fields.Selection( selection=[ @@ -93,3 +94,15 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = None + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError("Sold properties cannot be cancelled.") + record.state = "cancelled" + + def action_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError("Cancelled properties cannot be sold") + record.state = "sold" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index f3cc3946528..551c2ebdbc5 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from datetime import timedelta from odoo import api, fields, models +from odoo.exceptions import UserError class EstatePropertyOffer(models.Model): @@ -42,3 +43,18 @@ def _inverse_date_deadline(self): record.validity = ( record.date_deadline - fields.Date.to_date(record.create_date) ).days + + def action_refuse(self): + for record in self: + record.status = "refused" + + def action_accept(self): + for record in self: + # TODO: property state should be readonly and just check on state == 'offer_accepted' + if any(offer.status == 'accepted' for offer in record.property_id.offer_ids): + raise UserError("Property already has another accepted offer.") + # TODO: consider checking other property states + record.status = "accepted" + record.property_id.buyer = record.partner_id + record.property_id.selling_price = record.price + record.property_id.state = 'offer_accepted' diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index c53a97f4978..09a08b00093 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -25,6 +25,10 @@ + + + + +
+

+ +

+
+ + + + + + + + + + +
+ + estate.property.type.list + estate.property.type + + + + + + + + Property Types estate.property.type diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ae67979ff37..2ae01a0036c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -10,7 +10,7 @@ - + @@ -26,8 +26,9 @@
-
@@ -36,8 +37,8 @@ - - + + @@ -55,16 +56,15 @@ - - - + + - + @@ -82,7 +82,9 @@ estate.property.list estate.property - + @@ -92,7 +94,7 @@ - + @@ -101,5 +103,7 @@ Properties estate.property list,form + + {'search_default_available': True}