From 387119f46e4ec2868ad778721b1bf905e816e921 Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Mon, 17 Aug 2026 13:57:20 +0200 Subject: [PATCH 01/18] [ADD] estate: init module --- estate/__init__.py | 0 estate/__manifest__.py | 13 +++++++++++++ 2 files changed, 13 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..680d5d40ece --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Real Estate', + 'version': '0.1', + 'depends': [ + 'base', + ], + 'installable': True, + 'application': True, + 'author': 'emtal', + 'license': 'LGPL-3', +} \ No newline at end of file From 254215e9cff060043c78264bab1a82743928741e Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Mon, 17 Aug 2026 14:33:50 +0200 Subject: [PATCH 02/18] [IMP] estate: property model --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 20 ++++++++++++++++++++ 3 files changed, 22 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..9a7e03eded3 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..33102b29ea6 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,20 @@ +from odoo import models, fields + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property Model" + + name = fields.Char("Property Name", required = True) + description = fields.Text("Description") + postcode = fields.Char("Postcode") + date_availability = fields.Date("Availability Date") + expected_price = fields.Float("Expected Price", required = True) + selling_price = fields.Float("Selling Price") + bedrooms = fields.Integer("Num of Bedrooms") + living_area = fields.Integer("Num of Living Areas") + facades = fields.Integer("Facades") + garage = fields.Boolean("Garage") + garden = fields.Boolean("Garden") + garden_area = fields.Integer("Garden Area") + garden_orientation = fields.Selection(string="Garden Orientation", selection=[('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West')]) \ No newline at end of file From 809f2767bc1c66f147c90bc2f96d7927db4fc477 Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Mon, 17 Aug 2026 16:18:40 +0200 Subject: [PATCH 03/18] [IMP] estate: modify access rights 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 680d5d40ece..cbc9f4a0121 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,9 @@ 'depends': [ 'base', ], + 'data': [ + 'security/ir.model.access.csv', + ], 'installable': True, 'application': True, 'author': 'emtal', diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..68f45151f2b --- /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 +estate.property,estate.property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From 139ea62c0903b1eb3e4fe4898527e09e8e6399df Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Mon, 17 Aug 2026 17:05:58 +0200 Subject: [PATCH 04/18] [IMP] estate: view, filtering fields --- estate/__manifest__.py | 2 ++ estate/models/estate_property.py | 16 +++++++++------- estate/views/estate_menus.xml | 9 +++++++++ estate/views/estate_property_views.xml | 9 +++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index cbc9f4a0121..6cab1fec665 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,6 +8,8 @@ ], 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], 'installable': True, 'application': True, diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 33102b29ea6..4985332fe59 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,16 +5,18 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property Model" - name = fields.Char("Property Name", required = True) + name = fields.Char("Property Name", required=True) description = fields.Text("Description") postcode = fields.Char("Postcode") - date_availability = fields.Date("Availability Date") - expected_price = fields.Float("Expected Price", required = True) - selling_price = fields.Float("Selling Price") - bedrooms = fields.Integer("Num of Bedrooms") - living_area = fields.Integer("Num of Living Areas") + date_availability = fields.Date("Availability Date", default=fields.Date.add(fields.Date.today(), months=3), copy=False) + expected_price = fields.Float("Expected Price", required=True) + selling_price = fields.Float("Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer("Bedrooms", default=2) + living_area = fields.Integer("Living Area") facades = fields.Integer("Facades") garage = fields.Boolean("Garage") garden = fields.Boolean("Garden") garden_area = fields.Integer("Garden Area") - garden_orientation = fields.Selection(string="Garden Orientation", selection=[('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West')]) \ No newline at end of file + garden_orientation = fields.Selection(string="Garden Orientation", selection=[('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West')]) + state = fields.Selection(string="State", selection=[('new', 'New'), ('offer received', 'Offer Received'), ('offer accepted','Offer Accepted'), ('sold', 'Sold'), ('cancel', 'Cancelled')], default='new') + active = fields.Boolean("Active", default=True) \ 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..2d54d9a522b --- /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..a765539c0f5 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,9 @@ + + + + + Properties + estate.property + list + + \ No newline at end of file From bed29ea474137e62d12c0fc61eaa96c6364d87be Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Tue, 18 Aug 2026 13:15:11 +0200 Subject: [PATCH 05/18] [IMP] estate: ran ruff --- estate/__init__.py | 2 +- estate/__manifest__.py | 28 +++++++++++++--------------- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 32 +++++++++++++++++++++++++++----- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 6cab1fec665..8998fa0b001 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,18 +1,16 @@ -# -*- coding: utf-8 -*- - { - 'name': 'Real Estate', - 'version': '0.1', - 'depends': [ - 'base', + "name": "Real Estate", + "version": "0.1", + "depends": [ + "base", ], - 'data': [ - 'security/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_menus.xml', + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_menus.xml", ], - 'installable': True, - 'application': True, - 'author': 'emtal', - 'license': 'LGPL-3', -} \ No newline at end of file + "installable": True, + "application": True, + "author": "emtal", + "license": "LGPL-3", +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..5e1963c9d2f 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property \ No newline at end of file +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4985332fe59..ccd27267c82 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 fields, models class EstateProperty(models.Model): @@ -8,7 +8,11 @@ class EstateProperty(models.Model): name = fields.Char("Property Name", required=True) description = fields.Text("Description") postcode = fields.Char("Postcode") - date_availability = fields.Date("Availability Date", default=fields.Date.add(fields.Date.today(), months=3), copy=False) + date_availability = fields.Date( + "Availability Date", + default=fields.Date.add(fields.Date.today(), months=3), + copy=False, + ) expected_price = fields.Float("Expected Price", required=True) selling_price = fields.Float("Selling Price", readonly=True, copy=False) bedrooms = fields.Integer("Bedrooms", default=2) @@ -17,6 +21,24 @@ class EstateProperty(models.Model): garage = fields.Boolean("Garage") garden = fields.Boolean("Garden") garden_area = fields.Integer("Garden Area") - garden_orientation = fields.Selection(string="Garden Orientation", selection=[('north', 'North'), ('east', 'East'), ('south', 'South'), ('west', 'West')]) - state = fields.Selection(string="State", selection=[('new', 'New'), ('offer received', 'Offer Received'), ('offer accepted','Offer Accepted'), ('sold', 'Sold'), ('cancel', 'Cancelled')], default='new') - active = fields.Boolean("Active", default=True) \ No newline at end of file + garden_orientation = fields.Selection( + string="Garden Orientation", + selection=[ + ("north", "North"), + ("east", "East"), + ("south", "South"), + ("west", "West"), + ], + ) + state = fields.Selection( + string="State", + selection=[ + ("new", "New"), + ("offer received", "Offer Received"), + ("offer accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancel", "Cancelled"), + ], + default="new", + ) + active = fields.Boolean("Active", default=True) From 941764792dd7f7127f80ac3db51585f7d95ae069 Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Tue, 18 Aug 2026 13:52:49 +0200 Subject: [PATCH 06/18] [IMP] estate: list view, form and search bar --- estate/models/estate_property.py | 6 +- estate/views/estate_property_views.xml | 76 ++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ccd27267c82..0e1766d29c5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -10,7 +10,7 @@ class EstateProperty(models.Model): postcode = fields.Char("Postcode") date_availability = fields.Date( "Availability Date", - default=fields.Date.add(fields.Date.today(), months=3), + default=lambda self: fields.Date.add(fields.Date.today(), months=3), copy=False, ) expected_price = fields.Float("Expected Price", required=True) @@ -34,8 +34,8 @@ class EstateProperty(models.Model): string="State", selection=[ ("new", "New"), - ("offer received", "Offer Received"), - ("offer accepted", "Offer Accepted"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancel", "Cancelled"), ], diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index a765539c0f5..8cdf1582f42 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,4 +6,80 @@ estate.property list + + + estate.property.list + estate.property + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + \ No newline at end of file From d7e26cdd234f27f79e6d9a906c2c3a6935a897b2 Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Tue, 18 Aug 2026 15:41:21 +0200 Subject: [PATCH 07/18] [IMP] estate: model relationships (m2o, m2m, o2m) --- estate/__manifest__.py | 3 +++ estate/models/__init__.py | 7 +++++- estate/models/estate_property.py | 21 ++++++++++++++++++ estate/models/estate_property_offer.py | 19 ++++++++++++++++ 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 | 4 ++++ estate/views/estate_property_offer_views.xml | 14 ++++++++++++ estate/views/estate_property_tag_views.xml | 21 ++++++++++++++++++ estate/views/estate_property_type_views.xml | 23 ++++++++++++++++++++ estate/views/estate_property_views.xml | 20 +++++++++++++++-- 12 files changed, 149 insertions(+), 4 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 8998fa0b001..1c4c24f3325 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,9 @@ "data": [ "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", ], "installable": True, 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 0e1766d29c5..177f334b327 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -42,3 +42,24 @@ class EstateProperty(models.Model): default="new", ) active = fields.Boolean("Active", default=True) + + # Many2one references + type_id = fields.Many2one(comodel_name="estate.property.type") + buyer_id = fields.Many2one(string="Buyer", comodel_name="res.partner") + salesperson_id = fields.Many2one( + string="Salesperson", + comodel_name="res.users", + default=lambda self: self.env.user, + ) + + # One2many references + offer_ids = fields.One2many( + comodel_name="estate.property.offer", + inverse_name="property_id", + ) + + # Many2many references + tag_ids = fields.Many2many( + string="Tags", + comodel_name="estate.property.tag", + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..83e84ed620d --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer Model" + + price = fields.Float("Price") + status = fields.Selection( + string="Status", + selection=[ + ("accepted", "Accepted"), + ("refused", "Refused"), + ], + ) + + # Many2one references + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="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..d6a46609e81 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tag Model" + + name = fields.Char("Property Tag", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..78b64fcde25 --- /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 = "Estate Property Type Model" + + name = fields.Char("Property Type", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 68f45151f2b..da0da54f320 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 -estate.property,estate.property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +estate.property,estate.property,model_estate_property,base.group_user,1,1,1,1 +estate.property.type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +estate.property.tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 +estate.property.offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 2d54d9a522b..e45446f2310 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -5,5 +5,9 @@ + + + + \ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..82b47fadbac --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,14 @@ + + + + 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..37ea55e165b --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,21 @@ + + + + + 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..97504a39718 --- /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 8cdf1582f42..7509c324b96 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ Properties estate.property - list + list,form @@ -13,6 +13,8 @@ + + @@ -22,7 +24,7 @@ - + estate.property.form estate.property @@ -31,6 +33,7 @@

+ @@ -54,6 +57,19 @@ + + + + + + + + + + + + + From 490478a4a972de70e938da8ba4a1876c8a183f83 Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Tue, 18 Aug 2026 16:36:40 +0200 Subject: [PATCH 08/18] [IMP] estate: computed fields and onchanges --- estate/models/estate_property.py | 25 ++++++++++++++++- estate/models/estate_property_offer.py | 29 +++++++++++++++++++- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 1 + 4 files changed, 55 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 177f334b327..04144db0dee 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -24,6 +24,7 @@ class EstateProperty(models.Model): garden_orientation = fields.Selection( string="Garden Orientation", selection=[ + ("n/a", "N/A"), ("north", "North"), ("east", "East"), ("south", "South"), @@ -63,3 +64,25 @@ class EstateProperty(models.Model): string="Tags", comodel_name="estate.property.tag", ) + + # Computed + total_area = fields.Float("Total Area", compute="_compute_total_area") + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + best_price = fields.Float("Best Price", compute="_find_best_price") + + @api.depends("offer_ids.price") + def _find_best_price(self): + for record in self: + prices = record.offer_ids.mapped("price") + record.best_price = max(prices) if prices else 0.0 + + # On change + @api.onchange("garden") + def _onchange_garden(self): + self.garden_area = 10 if self.garden else 0 + self.garden_orientation = "north" if self.garden else "n/a" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 83e84ed620d..b8fbeba52ed 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): @@ -13,7 +15,32 @@ class EstatePropertyOffer(models.Model): ("refused", "Refused"), ], ) + validity = fields.Integer("Validity (days)", default=7) # Many2one references partner_id = fields.Many2one("res.partner", string="Partner", required=True) property_id = fields.Many2one("estate.property", string="Property", required=True) + + # Computed fields + date_deadline = fields.Date( + string="Deadline", + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + ) + + @api.depends("validity", "create_date") + def _compute_date_deadline(self): + for record in self: + base_date = ( + record.create_date.date() if record.create_date else fields.Date.today() + ) + record.date_deadline = base_date + timedelta(days=record.validity) + + @api.onchange("date_deadline") + def _inverse_date_deadline(self): + for record in self: + base_date = ( + record.create_date.date() if record.create_date else fields.Date.today() + ) + if record.date_deadline and base_date: + record.validity = (record.date_deadline - base_date).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 82b47fadbac..00df02e43af 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -7,6 +7,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7509c324b96..1bc812e350e 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -57,6 +57,7 @@ + From 36b7e1c77392e94ee5e70a0b41b355d7d4d1c82d Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Wed, 19 Aug 2026 09:20:59 +0200 Subject: [PATCH 09/18] [IMP] estate: Add functional buttons for ease operations --- estate/models/estate_property.py | 32 ++++++++++++++++++-- estate/models/estate_property_offer.py | 22 ++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 9 +++++- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 04144db0dee..0b295e42822 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import api, fields, models +from odoo.exceptions import UserError class EstateProperty(models.Model): @@ -38,15 +39,16 @@ class EstateProperty(models.Model): ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), - ("cancel", "Cancelled"), + ("cancelled", "Cancelled"), ], default="new", + copy=False, ) active = fields.Boolean("Active", default=True) # Many2one references type_id = fields.Many2one(comodel_name="estate.property.type") - buyer_id = fields.Many2one(string="Buyer", comodel_name="res.partner") + buyer_id = fields.Many2one(string="Buyer", comodel_name="res.partner", copy=False) salesperson_id = fields.Many2one( string="Salesperson", comodel_name="res.users", @@ -86,3 +88,29 @@ def _find_best_price(self): def _onchange_garden(self): self.garden_area = 10 if self.garden else 0 self.garden_orientation = "north" if self.garden else "n/a" + + def action_property_sold(self): + for record in self: + if record.state == "cancelled": + raise UserError( + "This property has already been cancelled. It can not be sold!" + ) + elif record.state == "sold": + raise UserError( + "This property has already been sold. It can not be sold again!" + ) + else: + record.state = "sold" + + def action_property_canceled(self): + for record in self: + if record.state == "sold": + raise UserError( + "This property has already been sold. It can not be cancelled!" + ) + elif record.state == "cancelled": + raise UserError( + "This property has already been cancelled. It can not be cancelled again!" + ) + else: + record.state = "cancelled" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b8fbeba52ed..a8879309f9a 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): @@ -14,6 +15,7 @@ class EstatePropertyOffer(models.Model): ("accepted", "Accepted"), ("refused", "Refused"), ], + copy=False, ) validity = fields.Integer("Validity (days)", default=7) @@ -44,3 +46,23 @@ def _inverse_date_deadline(self): ) if record.date_deadline and base_date: record.validity = (record.date_deadline - base_date).days + + def action_confirm_offer(self): + for offer in self: + # Check property have already been sold or cancelled + if offer.property_id.state in ["sold", "cancelled"]: + raise UserError("This property has already been sold or cancelled!") + + # Any accepted offer? + if any(o.status == "accepted" for o in offer.property_id.offer_ids): + raise UserError("An offer have already been accepted!") + + # Accept offer + offer.status = "accepted" + offer.property_id.state = "offer_accepted" + offer.property_id.selling_price = offer.price + offer.property_id.buyer_id = offer.partner_id + + def action_refuse_offer(self): + for offer in self: + offer.status = "refused" diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 00df02e43af..b4128395006 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -9,6 +9,8 @@ + + + + +

+ +

+
+ + + + + + + + + + + +
+ + + estate.property.type.tree + estate.property.type + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 37dc60c4879..02e70c9706f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -11,7 +11,11 @@ estate.property.list estate.property - + @@ -19,7 +23,7 @@ - +
@@ -30,11 +34,22 @@
-
@@ -42,12 +57,18 @@

- - + - + @@ -67,8 +88,8 @@ - - + + @@ -99,7 +120,11 @@ - + + + + Properties + estate.property + list,form + {'search_default_available_properties': True} + From dfa3997723a26c820399bf770a7dfb91f1ef5414 Mon Sep 17 00:00:00 2001 From: Emivvvvv Date: Wed, 19 Aug 2026 14:54:23 +0200 Subject: [PATCH 13/18] [IMP] estate: ovveride model functions, add properties field for res.users --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 25 +++++++++++++------- estate/models/estate_property_offer.py | 22 ++++++++++++++--- estate/models/res_users.py | 12 ++++++++++ estate/views/estate_property_offer_views.xml | 6 ++--- estate/views/estate_property_views.xml | 16 ++++++------- estate/views/res_users_views.xml | 16 +++++++++++++ 8 files changed, 77 insertions(+), 22 deletions(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d8dfbf10391..a0d546d975f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ "views/estate_property_offer_views.xml", "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", + "views/res_users_views.xml", "views/estate_menus.xml", ], "installable": True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 3683ff97b61..8d1b0ed8937 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,4 +3,5 @@ estate_property_offer, estate_property_tag, estate_property_type, + res_users, ) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9af2f840157..3cc79b8ab66 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -39,7 +39,7 @@ class EstateProperty(models.Model): ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), - ("cancelled", "Cancelled"), + ("canceled", "Canceled"), ], default="new", copy=False, @@ -90,9 +90,9 @@ def _onchange_garden(self): def action_property_sold(self): for record in self: - if record.state == "cancelled": + if record.state == "canceled": raise UserError( - "This property has already been cancelled. It can not be sold!" + "This property has already been canceled. It can not be sold!" ) elif record.state == "sold": raise UserError( @@ -105,20 +105,20 @@ def action_property_canceled(self): for record in self: if record.state == "sold": raise UserError( - "This property has already been sold. It can not be cancelled!" + "This property has already been sold. It can not be canceled!" ) - elif record.state == "cancelled": + elif record.state == "canceled": raise UserError( - "This property has already been cancelled. It can not be cancelled again!" + "This property has already been canceled. It can not be canceled again!" ) else: - record.state = "cancelled" + record.state = "canceled" # Constraints _check_expected_price = models.Constraint( "CHECK(expected_price >= 0)", "Expected price must be >= 0!" ) - _check_selling_price = models.Constraint( + _check_selling_price_positive = models.Constraint( "CHECK(selling_price >= 0)", "Selling price must be >= 0!" ) @@ -139,3 +139,12 @@ def _check_selling_price(self): raise UserError( "Selling price cannot be lower than 90% of the expected price!" ) + + # Model decorators + @api.ondelete(at_uninstall=False) + def _prevent_deletion_if_not_new_or_canceled(self): + for record in self: + if record.state not in ("new", "canceled"): + raise UserError( + "You cannot delete a property unless its state is 'New' or 'Canceled'." + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index a68bf3e59e4..8c88408129b 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -52,9 +52,9 @@ def _inverse_date_deadline(self): def action_confirm_offer(self): for offer in self: - # Check property have already been sold or cancelled - if offer.property_id.state in ["sold", "cancelled"]: - raise UserError("This property has already been sold or cancelled!") + # Check property have already been sold or canceled + if offer.property_id.state in ["sold", "canceled"]: + raise UserError("This property has already been sold or canceled!") # Any accepted offer? if any(o.status == "accepted" for o in offer.property_id.offer_ids): @@ -71,3 +71,19 @@ def action_refuse_offer(self): offer.status = "refused" _check_price = models.Constraint("CHECK(price >= 0)", "Offer price must be >= 0!") + + # Model decorators + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + property_rec = self.env["estate.property"].browse(vals["property_id"]) + + for offer in property_rec.offer_ids: + if vals.get("price", 0) <= offer.price: + raise UserError( + "The offer amount must be strictly higher than existing offers." + ) + + property_rec.state = "offer_received" + + return super().create(vals_list) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..026c8b0985d --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResUser(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + comodel_name="estate.property", + inverse_name="salesperson_id", + string="Properties", + domain=[("state", "in", ["new", "offer_received"])], + ) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 4c9e89c8fa7..47c7b07552c 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -26,14 +26,14 @@ string="Confirm" type="object" icon="fa-check" - invisible="status != None" + invisible="status" />