From 2be8a14b3f9e261a329591f626a6e3b9ddc46aad Mon Sep 17 00:00:00 2001 From: ahham Date: Mon, 16 Feb 2026 10:39:25 +0100 Subject: [PATCH 01/14] [ADD] estate: add estate property module declaration --- estate/__init__.py | 0 estate/__manifest__.py | 15 ++++++++++++++ estate/static/description/icon.png | Bin 0 -> 5636 bytes estate/static/description/icon.svg | 32 +++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/static/description/icon.png create mode 100644 estate/static/description/icon.svg 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..7599bbc4303 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,15 @@ +{ + 'name': 'Estate', + 'version': '0.0', + 'summary': 'Real Estate Management', + 'depends': [ + 'base', + ], + 'data': [ + + ], + + 'installable': True, + 'application': True, + 'author': 'ahham', +} \ No newline at end of file diff --git a/estate/static/description/icon.png b/estate/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0ceddd7d3e975b14e55005dc26e7fd96563b1591 GIT binary patch literal 5636 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 eRwvwwP5dtfk@|8;*@LJ60000 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 4d165da137fbf58011e5b0495c4dbe53e3fb14ae Mon Sep 17 00:00:00 2001 From: ahham Date: Mon, 16 Feb 2026 11:03:02 +0100 Subject: [PATCH 02/14] [IMP] estate: add estate_property model [LINT] estate: add linting and formatting [IMP] estate: add estate_property model --- estate/__init__.py | 1 + estate/__manifest__.py | 4 +--- estate/models/__init__.py | 1 + estate/models/estate_property.py | 24 ++++++++++++++++++++++++ 4 files changed, 27 insertions(+), 3 deletions(-) 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/__manifest__.py b/estate/__manifest__.py index 7599bbc4303..d404849c581 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,10 +6,8 @@ 'base', ], 'data': [ - ], - 'installable': True, 'application': True, 'author': 'ahham', -} \ No newline at end of file +} 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..05a9f44133d --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,24 @@ +from odoo import models, fields + +GARDEN_ORIENTATION = [("north", "North"), ("south", "South"), + ("east", "East"), ("west", "West")] + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" + + name = fields.Char(string="Name", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + date_availability = fields.Date(string="Date Availability") + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bedrooms") + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area") + garden_orientation = fields.Selection(string="Garden Orientation", + selection=GARDEN_ORIENTATION) From 426e4bc86fca947fa0f4a7815871360bfec0e382 Mon Sep 17 00:00:00 2001 From: ahham Date: Mon, 16 Feb 2026 13:06:37 +0100 Subject: [PATCH 03/14] [IMP] estate: add access control to estate_property model --- estate/__manifest__.py | 1 + estate/security/ir.model.access.csv | 2 ++ 2 files changed, 3 insertions(+) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d404849c581..3bf0d259c00 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,7 @@ 'base', ], 'data': [ + 'security/ir.model.access.csv', ], 'installable': True, 'application': True, diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..976b61e8cb3 --- /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 f0f5d2659beb1f3d76d12b029f0872991e7a723b Mon Sep 17 00:00:00 2001 From: ahham Date: Mon, 16 Feb 2026 14:41:30 +0100 Subject: [PATCH 04/14] [IMP] estate: add menus and action for estate_property --- estate/__manifest__.py | 2 ++ estate/models/estate_property.py | 13 ++++++++----- estate/views/estate_menus.xml | 8 ++++++++ estate/views/estate_property_views.xml | 9 +++++++++ 4 files changed, 27 insertions(+), 5 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 3bf0d259c00..3d93a19349a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,6 +7,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 05a9f44133d..228d5239f70 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,7 +1,8 @@ from odoo import models, fields +from odoo.tools.date_utils import relativedelta -GARDEN_ORIENTATION = [("north", "North"), ("south", "South"), - ("east", "East"), ("west", "West")] +GARDEN_ORIENTATION = [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")] +PROPERTY_STATE = [("new", "New"), ("offer_received", "Offer Received"), ("offer_accepted", "Offer Accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")] class EstateProperty(models.Model): @@ -11,10 +12,10 @@ class EstateProperty(models.Model): name = fields.Char(string="Name", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Date Availability") + date_availability = fields.Date(string="Date Availability", copy=False, default=lambda self: fields.Date.context_today(self) + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) - selling_price = fields.Float(string="Selling Price") - bedrooms = fields.Integer(string="Bedrooms") + selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) + bedrooms = fields.Integer(string="Bedrooms", default=2) living_area = fields.Integer(string="Living Area") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") @@ -22,3 +23,5 @@ class EstateProperty(models.Model): garden_area = fields.Integer(string="Garden Area") garden_orientation = fields.Selection(string="Garden Orientation", selection=GARDEN_ORIENTATION) + active = fields.Boolean(string="Active", default=True) + state = fields.Selection(string="Status", selection=PROPERTY_STATE, required=True, copy=False, default="new") diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..9b158c63e02 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..e01bce85a95 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,9 @@ + + + + + Properties + estate.property + list,form + + \ No newline at end of file From f37f97789245db5f59a707152cec39996c13cf29 Mon Sep 17 00:00:00 2001 From: ahham Date: Mon, 16 Feb 2026 16:09:52 +0100 Subject: [PATCH 05/14] [IMP] estate: add list,form and search custom views for estate_property --- estate/__manifest__.py | 3 +- estate/models/estate_property.py | 6 +-- estate/views/estate_property_views.xml | 73 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3d93a19349a..b523f8485ca 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,7 +10,6 @@ 'views/estate_property_views.xml', 'views/estate_menus.xml' ], - 'installable': True, 'application': True, - 'author': 'ahham', + 'author': 'Odoo S.A.', } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 228d5239f70..afc9ead8fee 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,18 +9,18 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property" - name = fields.Char(string="Name", required=True) + name = fields.Char(string="Title", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") date_availability = fields.Date(string="Date Availability", copy=False, default=lambda self: fields.Date.context_today(self) + relativedelta(months=3)) expected_price = fields.Float(string="Expected Price", required=True) selling_price = fields.Float(string="Selling Price", readonly=True, copy=False) bedrooms = fields.Integer(string="Bedrooms", default=2) - living_area = fields.Integer(string="Living Area") + living_area = fields.Integer(string="Living Area (sqm)") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") garden = fields.Boolean(string="Garden") - garden_area = fields.Integer(string="Garden Area") + garden_area = fields.Integer(string="Garden Area (sqm)") garden_orientation = fields.Selection(string="Garden Orientation", selection=GARDEN_ORIENTATION) active = fields.Boolean(string="Active", default=True) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index e01bce85a95..0239cf84ede 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -6,4 +6,77 @@ estate.property list,form + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + estate.property.search + estate.property + + + + + + + + + + + + + + + \ No newline at end of file From 332eef48fa596204d38d2692efbc620960cf370a Mon Sep 17 00:00:00 2001 From: ahham Date: Tue, 17 Feb 2026 10:27:39 +0100 Subject: [PATCH 06/14] [IMP] estate: add property type,tag and offers and defined their relationship with estate_property --- estate/__manifest__.py | 5 ++++- estate/models/__init__.py | 3 +++ estate/models/estate_property.py | 6 ++++++ estate/models/estate_property_offer.py | 12 ++++++++++++ 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 | 6 +++++- estate/views/estate_property_offer_views.xml | 15 +++++++++++++++ estate/views/estate_property_tag_views.xml | 9 +++++++++ estate/views/estate_property_type_views.xml | 9 +++++++++ estate/views/estate_property_views.xml | 15 +++++++++++++-- 12 files changed, 96 insertions(+), 5 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 b523f8485ca..4c2f83995c7 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,7 +8,10 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_menus.xml' + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_menus.xml', + 'views/estate_property_offer_views.xml', ], 'application': True, 'author': 'Odoo S.A.', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index afc9ead8fee..6c57848ca79 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -25,3 +25,9 @@ class EstateProperty(models.Model): selection=GARDEN_ORIENTATION) active = fields.Boolean(string="Active", default=True) state = fields.Selection(string="Status", selection=PROPERTY_STATE, required=True, copy=False, default="new") + + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + buyer_id = fields.Many2one("res.partner", string="Buyer") + salesman_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) + property_tag_ids = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many("estate.property.offer", "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..60955fed0dc --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,12 @@ +from odoo import models, fields + +PROPERTY_OFFER_STATE = [("accepted", "Accepted"), ("refused", "Refused")] +class PropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + price = fields.Float() + status = fields.Selection(selection=PROPERTY_OFFER_STATE, copy=False) + + partner_id = fields.Many2one("res.partner", required=True) + property_id = fields.Many2one("estate.property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..786a4a9462e --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class PropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real Estate Property Tag" + + name = fields.Char(string="Name", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..61acc863f27 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class PropertyType(models.Model): + _name = "estate.property.type" + _description = "Real Estate Property Type" + + name = fields.Char(string="Property Type", required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 976b61e8cb3..4c593ed42e4 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 9b158c63e02..b3476fd443e 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,8 +1,12 @@ - + + + + + \ 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..dd7c59dffeb --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,15 @@ + + + + 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..5242ced6a1e --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,9 @@ + + + + Property Tags + estate.property.tag + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 00000000000..9e1842b15a7 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,9 @@ + + + + Property Types + estate.property.type + list,form + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0239cf84ede..c0271ef4864 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -33,8 +33,10 @@ + + @@ -56,13 +58,22 @@ + + + + + + + + + - estate.property.search + estate.property.view.search estate.property @@ -73,7 +84,7 @@ - + From 02cf3dcf88e80cc5099ad7ed8d84ba152da56617 Mon Sep 17 00:00:00 2001 From: ahham Date: Tue, 17 Feb 2026 11:16:17 +0100 Subject: [PATCH 07/14] [IMP] estate: add computed fields and set onchange method --- estate/models/estate_property.py | 24 +++++++++++++++++++- estate/models/estate_property_offer.py | 18 ++++++++++++++- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 6c57848ca79..9dc4d57a90e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import models, fields, api from odoo.tools.date_utils import relativedelta GARDEN_ORIENTATION = [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")] @@ -31,3 +31,25 @@ class EstateProperty(models.Model): salesman_id = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) property_tag_ids = fields.Many2many("estate.property.tag") offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + + total_area = fields.Integer(string="Total Area (sqm)", compute="_compute_total_area") + best_offer = fields.Float(string="Best Offer", 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.price") + def _compute_best_offer(self): + for record in self: + record.best_offer = max(record.offer_ids.mapped("price"), default=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 = False diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 60955fed0dc..03ce5fe2051 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,9 @@ -from odoo import models, fields +from odoo import models, fields, api +from odoo.tools.date_utils import relativedelta PROPERTY_OFFER_STATE = [("accepted", "Accepted"), ("refused", "Refused")] + + class PropertyOffer(models.Model): _name = "estate.property.offer" _description = "Real Estate Property Offer" @@ -10,3 +13,16 @@ class PropertyOffer(models.Model): partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True) + + validity = fields.Integer(string="Validity (days)", default=7) + date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + @api.depends("validity") + def _compute_date_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = record.create_date + relativedelta(days=record.validity) + def _inverse_date_deadline(self): + for record in self: + if record.create_date and record.date_deadline: + record.validity = (record.date_deadline - record.create_date.date()).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index dd7c59dffeb..1e9705c3a67 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c0271ef4864..034e68e836b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -42,6 +42,7 @@ + @@ -56,6 +57,7 @@ + From 3245306607bbc5eab15109d098ef6ad70ba02ac1 Mon Sep 17 00:00:00 2001 From: ahham Date: Tue, 17 Feb 2026 13:25:39 +0100 Subject: [PATCH 08/14] [IMP] estate: add actions for property offers and property state --- estate/models/estate_property.py | 16 ++++++++++++++++ estate/models/estate_property_offer.py | 14 ++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 5 +++++ 4 files changed, 37 insertions(+) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9dc4d57a90e..385fa970064 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import models, fields, api +from odoo.exceptions import UserError from odoo.tools.date_utils import relativedelta GARDEN_ORIENTATION = [("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")] @@ -53,3 +54,18 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = False + + def action_cancel_property(self): + for record in self: + if record.state == "sold": + raise UserError("Sold properties cannot be cancelled") + else: + record.state = "cancelled" + return True + def action_sold_property(self): + for record in self: + if record.state == "cancelled": + raise UserError("Cancelled properties cannot be sold") + else: + record.state = "sold" + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 03ce5fe2051..366beb49e1e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -26,3 +26,17 @@ def _inverse_date_deadline(self): for record in self: if record.create_date and record.date_deadline: record.validity = (record.date_deadline - record.create_date.date()).days + + def action_accept_offer(self): + for record in self: + record.status = "accepted" + record.property_id.buyer_id = record.partner_id + record.property_id.selling_price = record.price + for offer in record.property_id.offer_ids: + if offer != record: + offer.status = "refused" + return True + def action_refuse_offer(self): + for record in self: + record.status = "refused" + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 1e9705c3a67..ab719002b0f 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -10,6 +10,8 @@ + + +
+

+ +

+ +
+ + + + + + + + + + + + + +
+ + + 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 26034e4029d..46675b1a6aa 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,19 +5,20 @@ Properties estate.property list,form + {'search_default_available': True, 'search_default_current': True} estate.property.list estate.property - + - + @@ -29,19 +30,20 @@

- +
- - + @@ -60,13 +62,13 @@ - - + + - + @@ -88,7 +90,7 @@ - + From 6f34f6932af5888f4e36889106219e5104a0391b Mon Sep 17 00:00:00 2001 From: ahham Date: Wed, 18 Feb 2026 10:59:34 +0100 Subject: [PATCH 11/14] [IMP] estate: override CRUD methods and extend user model to have properties page in form view --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 9 +++++++++ estate/models/estate_property_offer.py | 16 ++++++++++++---- estate/models/res_users.py | 14 ++++++++++++++ estate/views/res_users_views.xml | 16 ++++++++++++++++ 6 files changed, 53 insertions(+), 4 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 5cac28d76c3..c60b7eff130 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,7 @@ 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_menus.xml', + 'views/res_users_views.xml' ], 'application': True, 'author': 'Odoo S.A.', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index df4fb9e0826..315b1948ff6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -90,6 +90,15 @@ def _onchange_garden(self): self.garden_area = 0 self.garden_orientation = False + # ------------------------------------------------------------------------- + # ORM methods + # ------------------------------------------------------------------------- + @api.ondelete(at_uninstall=False) + def _unlink_new_cancelled_property(self): + for property in self: + if property.state not in ["new", "cancelled"]: + raise UserError("Only new and canceled properties can be deleted") + # ------------------------------------------------------------------------- # Action methods # ------------------------------------------------------------------------- diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5a84f057087..4e69492a88e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,7 @@ from odoo import models, fields, api +from odoo.exceptions import UserError from odoo.tools.date_utils import relativedelta - +from odoo.tools.float_utils import float_compare PROPERTY_OFFER_STATE = [("accepted", "Accepted"), ("refused", "Refused")] @@ -61,10 +62,17 @@ def _inverse_date_deadline(self): # ------------------------------------------------------------------------- @api.model def create(self, vals): + for val in vals: + property_record = self.env["estate.property"].browse(val.get("property_id")) + + existing_prices = property_record.offer_ids.mapped("price") + max_property_offers = max(existing_prices) if existing_prices else 0 + print(max_property_offers) + if float_compare(val.get("price"), max_property_offers, precision_digits=2) <= 0: + raise UserError(f"The offer must be higher than {max_property_offers}") offer = super().create(vals) - property_record = offer.property_id - if property_record.state == 'new': - property_record.state = 'offer_received' + if property_record.state == "new": + property_record.state = "offer_received" return offer # ------------------------------------------------------------------------- diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..420731a723f --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,14 @@ +from odoo import models, fields + + +class ResUsers(models.Model): + + # ------------------------------------------------------------------------- + # Private attributes + # ------------------------------------------------------------------------- + _inherit = "res.users" + + # ------------------------------------------------------------------------- + # Field declarations + # ------------------------------------------------------------------------- + property_ids = fields.One2many("estate.property", "salesman_id", string="Properties", domain="[('state', 'in', ('new', 'offer_received'))]") diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..b25e80b0bb3 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,16 @@ + + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + From 5510c9865b5ce8d722e8e000fb315bc708de2561 Mon Sep 17 00:00:00 2001 From: ahham Date: Wed, 18 Feb 2026 11:30:54 +0100 Subject: [PATCH 12/14] [ADD] estate_account: override sold action to create invoice for buyer --- estate/models/estate_property_offer.py | 11 ++++---- estate/models/res_users.py | 2 +- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 16 ++++++++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 32 ++++++++++++++++++++++++ 6 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4e69492a88e..713cc42eedd 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -67,13 +67,14 @@ def create(self, vals): existing_prices = property_record.offer_ids.mapped("price") max_property_offers = max(existing_prices) if existing_prices else 0 - print(max_property_offers) if float_compare(val.get("price"), max_property_offers, precision_digits=2) <= 0: raise UserError(f"The offer must be higher than {max_property_offers}") - offer = super().create(vals) - if property_record.state == "new": - property_record.state = "offer_received" - return offer + + offers = super().create(vals) + for offer in offers: + if offer.property_id.state == "new": + offer.property_id.state = "offer_received" + return offers # ------------------------------------------------------------------------- # Action methods diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 420731a723f..a7523ad17d9 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -11,4 +11,4 @@ class ResUsers(models.Model): # ------------------------------------------------------------------------- # Field declarations # ------------------------------------------------------------------------- - property_ids = fields.One2many("estate.property", "salesman_id", string="Properties", domain="[('state', 'in', ('new', 'offer_received'))]") + property_ids = fields.One2many("estate.property", "salesman_id", string="Estate Properties", domain="[('state', 'in', ('new', 'offer_received'))]") diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..9cb94cbb30d --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,16 @@ +{ + 'name': 'Estate Account', + 'version': '0.0', + 'summary': 'Real Estate Management with Account', + 'depends': [ + 'base', + 'estate', + 'account', + ], + 'data': [ + + ], + 'application': True, + 'author': 'Odoo S.A.', + 'license': 'LGPL-3', +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..51516fc4bdd --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,32 @@ +from odoo import models +from odoo.orm.commands import Command + + +class EstateProperty(models.Model): + + # ------------------------------------------------------------------------- + # Private attributes + # ------------------------------------------------------------------------- + _inherit = "estate.property" + + # ------------------------------------------------------------------------- + # Action methods + # ------------------------------------------------------------------------- + def action_sold_property(self): + self.env["account.move"].create({ + "partner_id": self.buyer_id.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create({ + "name": self.name, + "quantity": 0.06, + "price_unit": self.selling_price, + }), + Command.create({ + "name": "Administrative fees", + "quantity": 1, + "price_unit": 100.00, + }) + ], + }) + return super().action_sold_property() From d4b9b4780fa36639429b4b4b08143a485272ed5a Mon Sep 17 00:00:00 2001 From: ahham Date: Wed, 18 Feb 2026 13:44:56 +0100 Subject: [PATCH 13/14] [IMP] estate: add kanban view for estate_property --- estate/models/estate_property_offer.py | 11 +++----- estate/models/estate_property_type.py | 4 +-- estate/views/estate_property_type_views.xml | 4 +-- estate/views/estate_property_views.xml | 28 ++++++++++++++++++++- estate/views/res_users_views.xml | 4 +-- estate_account/__manifest__.py | 1 - estate_account/models/estate_property.py | 4 +-- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 713cc42eedd..4f9ca5bddb5 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -60,7 +60,7 @@ def _inverse_date_deadline(self): # ------------------------------------------------------------------------- # ORM methods # ------------------------------------------------------------------------- - @api.model + @api.model_create_multi def create(self, vals): for val in vals: property_record = self.env["estate.property"].browse(val.get("property_id")) @@ -69,12 +69,9 @@ def create(self, vals): max_property_offers = max(existing_prices) if existing_prices else 0 if float_compare(val.get("price"), max_property_offers, precision_digits=2) <= 0: raise UserError(f"The offer must be higher than {max_property_offers}") - - offers = super().create(vals) - for offer in offers: - if offer.property_id.state == "new": - offer.property_id.state = "offer_received" - return offers + if property_record.state == "new": + property_record.state = "offer_received" + return super().create(vals) # ------------------------------------------------------------------------- # Action methods diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index e917052fd88..deebe37532b 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -8,14 +8,14 @@ class PropertyType(models.Model): # ------------------------------------------------------------------------- _name = "estate.property.type" _description = "Real Estate Property Type" - _order = "name" + _order = "sequence, name" # ------------------------------------------------------------------------- # Field declarations # ------------------------------------------------------------------------- name = fields.Char(string="Property Type", required=True) property_ids = fields.One2many("estate.property", "property_type_id", string="Properties") - sequence = fields.Integer('Sequence', default=1) + sequence = fields.Integer('Sequence', default=10) offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") offer_count = fields.Integer(string="Offer Count", compute="_compute_offer_count") diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 7fd4c277b34..d23fc0f367c 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -14,9 +14,7 @@
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 46675b1a6aa..a91d05dd879 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_available': True, 'search_default_current': True} @@ -99,4 +99,30 @@ + + estate.property.view.kanban + estate.property + + + + + + + + +
+ Expected Price: +
+
+ Best Offer: +
+
+ Selling Price: +
+ +
+
+
+
+
\ No newline at end of file diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index b25e80b0bb3..1896b67e822 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -6,11 +6,11 @@ res.users - + - + diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 9cb94cbb30d..f605bbdcdcb 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -3,7 +3,6 @@ 'version': '0.0', 'summary': 'Real Estate Management with Account', 'depends': [ - 'base', 'estate', 'account', ], diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 51516fc4bdd..2f30024ae33 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -19,8 +19,8 @@ def action_sold_property(self): "invoice_line_ids": [ Command.create({ "name": self.name, - "quantity": 0.06, - "price_unit": self.selling_price, + "quantity": 1, + "price_unit": self.selling_price * 0.06, }), Command.create({ "name": "Administrative fees", From 8bfdeb3bcf88fc3a19420a3b5cd26fff3ff144ee Mon Sep 17 00:00:00 2001 From: ahham Date: Fri, 20 Feb 2026 10:08:17 +0100 Subject: [PATCH 14/14] [IMP] Discover the javascript framework chapter --- awesome_owl/__manifest__.py | 1 + .../static/src/components/card/card.js | 15 ++++++++ .../static/src/components/card/card.xml | 14 ++++++++ .../static/src/components/counter/counter.js | 19 ++++++++++ .../static/src/components/counter/counter.xml | 12 +++++++ .../components/todoo/todo_item/todo_item.js | 17 +++++++++ .../components/todoo/todo_item/todo_item.xml | 13 +++++++ .../components/todoo/todo_list/todo_list.js | 36 +++++++++++++++++++ .../components/todoo/todo_list/todo_list.xml | 12 +++++++ awesome_owl/static/src/playground.js | 16 +++++++-- awesome_owl/static/src/playground.xml | 22 ++++++++++-- awesome_owl/static/src/utils.js | 7 ++++ 12 files changed, 179 insertions(+), 5 deletions(-) create mode 100644 awesome_owl/static/src/components/card/card.js create mode 100644 awesome_owl/static/src/components/card/card.xml create mode 100644 awesome_owl/static/src/components/counter/counter.js create mode 100644 awesome_owl/static/src/components/counter/counter.xml create mode 100644 awesome_owl/static/src/components/todoo/todo_item/todo_item.js create mode 100644 awesome_owl/static/src/components/todoo/todo_item/todo_item.xml create mode 100644 awesome_owl/static/src/components/todoo/todo_list/todo_list.js create mode 100644 awesome_owl/static/src/components/todoo/todo_list/todo_list.xml create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py index 55002ab81de..36b81016d86 100644 --- a/awesome_owl/__manifest__.py +++ b/awesome_owl/__manifest__.py @@ -34,6 +34,7 @@ 'web/static/lib/bootstrap/scss/_variables.scss', 'web/static/lib/bootstrap/scss/_maps.scss', ('include', 'web._assets_bootstrap'), + ('include', 'web._assets_bootstrap_backend'), ('include', 'web._assets_core'), 'web/static/src/libs/fontawesome/css/font-awesome.css', 'awesome_owl/static/src/**/*', diff --git a/awesome_owl/static/src/components/card/card.js b/awesome_owl/static/src/components/card/card.js new file mode 100644 index 00000000000..f1bd3459f1b --- /dev/null +++ b/awesome_owl/static/src/components/card/card.js @@ -0,0 +1,15 @@ +import { Component, useState } from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.Card"; + static props = { + title: String, + slots: Object, + }; + setup() { + this.state = useState({ isOpen: true }); + } + toggleOpen() { + this.state.isOpen = !this.state.isOpen; + } +} diff --git a/awesome_owl/static/src/components/card/card.xml b/awesome_owl/static/src/components/card/card.xml new file mode 100644 index 00000000000..e4e351e115d --- /dev/null +++ b/awesome_owl/static/src/components/card/card.xml @@ -0,0 +1,14 @@ + + + +
+
+
+ + +
+ +
+
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/components/counter/counter.js b/awesome_owl/static/src/components/counter/counter.js new file mode 100644 index 00000000000..b351d71572e --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.js @@ -0,0 +1,19 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.Counter"; + static props = { + onChange: { type: Function, optional: true }, + }; + + setup() { + this.state = useState({ value: 0 }); + } + + increment() { + this.state.value++; + if (this.props.onChange) { + this.props.onChange(); + } + } +} diff --git a/awesome_owl/static/src/components/counter/counter.xml b/awesome_owl/static/src/components/counter/counter.xml new file mode 100644 index 00000000000..af8487139e8 --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.xml @@ -0,0 +1,12 @@ + + + + +
+ Counter: + + +
+
+ +
diff --git a/awesome_owl/static/src/components/todoo/todo_item/todo_item.js b/awesome_owl/static/src/components/todoo/todo_item/todo_item.js new file mode 100644 index 00000000000..b7d01989fd2 --- /dev/null +++ b/awesome_owl/static/src/components/todoo/todo_item/todo_item.js @@ -0,0 +1,17 @@ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.TodoItem"; + static props = { + todo: { + type: Object, + shape: { + id: Number, + description: String, + isCompleted: Boolean, + }, + }, + toggleState: Function, + removeTodo: Function, + }; +} diff --git a/awesome_owl/static/src/components/todoo/todo_item/todo_item.xml b/awesome_owl/static/src/components/todoo/todo_item/todo_item.xml new file mode 100644 index 00000000000..7d9c55541a3 --- /dev/null +++ b/awesome_owl/static/src/components/todoo/todo_item/todo_item.xml @@ -0,0 +1,13 @@ + + + +
+ + +. + + + +
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/components/todoo/todo_list/todo_list.js b/awesome_owl/static/src/components/todoo/todo_list/todo_list.js new file mode 100644 index 00000000000..d6ced3e65f8 --- /dev/null +++ b/awesome_owl/static/src/components/todoo/todo_list/todo_list.js @@ -0,0 +1,36 @@ +import { Component, useState } from "@odoo/owl"; +import { TodoItem } from "../todo_item/todo_item"; +import { useAutofocus } from "../../../utils"; +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + static components = { TodoItem }; + setup() { + this.todos = useState([]); + useAutofocus("todo_input"); + } + addTodo(event) { + if (event.keyCode === 13 && event.target.value.length) { + this.todos.push({ + id: this.todos.length + ? this.todos[this.todos.length - 1].id + 1 + : 1, + description: event.target.value, + isCompleted: false, + }); + event.target.value = ""; + } + } + toggleState(id) { + const todo = this.todos.find((todo) => todo.id === id); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + removeTodo(id) { + const index = this.todos.findIndex((todo) => todo.id === id); + if (index !== -1) { + this.todos.splice(index, 1); + } + } +} diff --git a/awesome_owl/static/src/components/todoo/todo_list/todo_list.xml b/awesome_owl/static/src/components/todoo/todo_list/todo_list.xml new file mode 100644 index 00000000000..d29bb302d2c --- /dev/null +++ b/awesome_owl/static/src/components/todoo/todo_list/todo_list.xml @@ -0,0 +1,12 @@ + + + +
+ + + + + +
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..1340e554b8e 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,15 @@ -import { Component } from "@odoo/owl"; - +import { Component, useState } from "@odoo/owl"; +import { Counter } from "./components/counter/counter"; +import { Card } from "./components/card/card"; +import { TodoList } from "./components/todoo/todo_list/todo_list"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.Playground"; + static components = { Counter, Card, TodoList }; + + setup() { + this.sum = useState({ value: 0 }); + } + incrementSum() { + this.sum.value++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..f14317744b1 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,28 @@ - + +
+
+ hello world +
+ + + +
- hello world + Sum: +
+
+ + Card 1 content + + + +
+

Todo List

+
diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..a58f34411a0 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,7 @@ +import { useRef, onMounted } from "@odoo/owl"; +export const useAutofocus = (ref) => { + const inputRef = useRef(ref); + onMounted(() => { + inputRef.el.focus(); + }); +};