From d5d906d1ec7c027aded5c84dd66f1f22f71ec177 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Mon, 16 Mar 2026 13:14:58 +0100 Subject: [PATCH 01/29] [IMP] estate: Chapter 2 --- estate/__init__.py | 0 estate/__manifest__.py | 21 +++++++++++++++++++++ 2 files changed, 21 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..803d42d3783 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,21 @@ +{ + 'name': "Real State", + 'version': '1.0', + 'depends': ['base'], + 'author': "kiro", + 'category': 'Category', + 'description': """ + Description text + """, + # data files always loaded at installation + 'data': [ + 'views/mymodule_view.xml', + ], + # data files containing optionally loaded demonstration data + 'demo': [ + 'demo/demo_data.xml', + ], + 'application': True, + 'category': 'Tutorials', + 'installable': True +} \ No newline at end of file From cf959b4c82da4f5fad84242e58e318b935e1c124 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Mon, 16 Mar 2026 13:47:12 +0100 Subject: [PATCH 02/29] [IMP] estate: Chapter 3 --- estate/__init__.py | 1 + estate/__manifest__.py | 8 -------- estate/models/__init__.py | 1 + estate/models/estate_property.py | 33 ++++++++++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 8 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..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/__manifest__.py b/estate/__manifest__.py index 803d42d3783..78b3a5f8be3 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,14 +7,6 @@ 'description': """ Description text """, - # data files always loaded at installation - 'data': [ - 'views/mymodule_view.xml', - ], - # data files containing optionally loaded demonstration data - 'demo': [ - 'demo/demo_data.xml', - ], 'application': True, 'category': 'Tutorials', 'installable': True 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..772df246de6 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,33 @@ +from odoo import models, fields + + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property model" + + + name = fields.Char('Name', required = True) + description = fields.Text('description') + + postcode = fields.Char() + date_availability = fields.Date + expected_price = fields.Float() + selling_price = fields.Float() + bedrooms = fields.Integer("# of bedrooms") + living_area = fields.Integer("") + facades = fields.Integer("# facades") + garage = fields.Boolean("Has garage") + garden = fields.Boolean("Has garden") + garden_orientation = fields.Selection( + selection=[ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West'), + ], + string="Garden Orientation" + ) + + + + From b73e0fc373784d99e274c7ba68425fe3657e6e9b Mon Sep 17 00:00:00 2001 From: kiro6 Date: Mon, 16 Mar 2026 14:11:31 +0100 Subject: [PATCH 03/29] [IMP] estate: Chapter 3 & 4 --- estate/__manifest__.py | 5 ++++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 78b3a5f8be3..d5b7e6f6c06 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,5 +9,8 @@ """, 'application': True, 'category': 'Tutorials', - 'installable': True + 'installable': True, + 'data': [ + 'security/ir.model.access.csv' + ] } \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..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 f204eeef7953c842e0d31a1fa5b41ac4e4bb8649 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Mon, 16 Mar 2026 15:26:02 +0100 Subject: [PATCH 04/29] [IMP] estate: Chapter 5 --- estate/__manifest__.py | 4 +-- estate/models/estate_property.py | 44 ++++++++++++++++++-------- estate/views/estate_property_views.xml | 19 +++++++++++ 3 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index d5b7e6f6c06..367039934bd 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,9 +8,9 @@ Description text """, 'application': True, - 'category': 'Tutorials', 'installable': True, 'data': [ - 'security/ir.model.access.csv' + 'security/ir.model.access.csv', + 'views/estate_property_views.xml' ] } \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 772df246de6..098dc508aba 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,29 +5,45 @@ class EstateProperty(models.Model): _name = "estate.property" _description = "Estate Property model" - - name = fields.Char('Name', required = True) - description = fields.Text('description') + name = fields.Char("Name", required=True) + description = fields.Text("description") postcode = fields.Char() - date_availability = fields.Date + date_availability = fields.Date( + string="Available From", + copy=False, + default=fields.Date.add(fields.Date.today(), days=90), + ) + expected_price = fields.Float() - selling_price = fields.Float() - bedrooms = fields.Integer("# of bedrooms") + + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer("# of bedrooms", default=0) + living_area = fields.Integer("") facades = fields.Integer("# facades") + garage = fields.Boolean("Has garage") garden = fields.Boolean("Has garden") + garden_orientation = fields.Selection( selection=[ - ('north', 'North'), - ('south', 'South'), - ('east', 'East'), - ('west', 'West'), + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), ], - string="Garden Orientation" + string="Garden Orientation", ) - - - + active = fields.Boolean(default=True) + state = fields.Selection( + selection=[ + ("new", "New"), + ("offer_received", "Offer Received"), + ("offer_accepted", "Offer Accepted"), + ("sold", "Sold"), + ("cancelled", "Cancelled"), + ], + string="Status", + ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..1cc23482b74 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,19 @@ + + + + + Properties + estate.property + list,form + + + + + + + + + + + + \ No newline at end of file From f7ddc92700ce58c9acef36bc6281427afb6785b0 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 17 Mar 2026 09:37:53 +0100 Subject: [PATCH 05/29] [IMP] estate: Chapter 6 --- estate/__manifest__.py | 3 +- estate/models/estate_property.py | 7 ++- estate/views/estate_menus.xml | 18 ++++++ estate/views/estate_property_views.xml | 82 +++++++++++++++++++++++--- 4 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 estate/views/estate_menus.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 367039934bd..e94b70e9ce8 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,6 +11,7 @@ 'installable': True, 'data': [ 'security/ir.model.access.csv', - 'views/estate_property_views.xml' + 'views/estate_property_views.xml', + 'views/estate_menus.xml' ] } \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 098dc508aba..b9304257b77 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -18,10 +18,10 @@ class EstateProperty(models.Model): expected_price = fields.Float() selling_price = fields.Float(readonly=True, copy=False) - bedrooms = fields.Integer("# of bedrooms", default=0) + bedrooms = fields.Integer("Bedrooms", default=0) - living_area = fields.Integer("") - facades = fields.Integer("# facades") + living_area = fields.Integer("Living_Area(sqm)") + facades = fields.Integer("Facades") garage = fields.Boolean("Has garage") garden = fields.Boolean("Has garden") @@ -36,6 +36,7 @@ class EstateProperty(models.Model): string="Garden Orientation", ) + last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now) active = fields.Boolean(default=True) state = fields.Selection( selection=[ diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..aa3d186a79f --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,18 @@ + + + + + + Properties + estate.property + 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 1cc23482b74..067fa566d52 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,18 +1,82 @@ - - Properties - estate.property - list,form - - - - + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.list + estate.property + + + + + + + + + + - + + estate.property.search + estate.property + + + + + + + + + + + + + + + +
From f03c6a546dc7214e6c8c8a4e2137cf07486d89e5 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 17 Mar 2026 10:02:25 +0100 Subject: [PATCH 06/29] [FIX] estate: Chapter 6 --- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 4 ++++ estate/models/estate_property_type.py | 9 +++++++++ estate/security/ir.model.access.csv | 3 ++- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 1 + 6 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 estate/models/estate_property_type.py diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..4a4300624a2 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, estate_property_type \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index b9304257b77..5a642f8d503 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -48,3 +48,7 @@ class EstateProperty(models.Model): ], string="Status", ) + + + partner_id = fields.Many2one("res.partner", string="Partner") + diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..6aa1466febd --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,9 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type Model" + + + name = fields.Char(required = True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 976b61e8cb3..ac245eddbb7 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,3 @@ 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 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index aa3d186a79f..73c23876cae 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,6 +2,11 @@ + + """ + Estate Property + """ + Properties estate.property @@ -14,5 +19,7 @@ + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 067fa566d52..a0bc47fdc25 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -18,6 +18,7 @@ + From 22f9301dea96492ec3631e8deb12efed83c5c30b Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 17 Mar 2026 11:40:46 +0100 Subject: [PATCH 07/29] [IMP] estate: Chapter 7 --- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 14 ++++++- estate/models/estate_property_offer.py | 19 ++++++++++ estate/models/estate_property_tag.py | 9 +++++ estate/security/ir.model.access.csv | 4 +- estate/views/estate_menus.xml | 52 ++++++++++++++++++++------ estate/views/estate_property_views.xml | 42 ++++++++++++++++----- 7 files changed, 117 insertions(+), 25 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 4a4300624a2..208a6195803 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_type \ No newline at end of file +from . import estate_property, estate_property_type , estate_property_tag , estate_property_offer \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 5a642f8d503..5a8815d5d92 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -50,5 +50,17 @@ class EstateProperty(models.Model): ) - partner_id = fields.Many2one("res.partner", string="Partner") + salesman_id = fields.Many2one("res.users", string="Salesman", default =lambda self: self.env.user ) + buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False ) + property_type_id = fields.Many2one("estate.property.type", string="Property Type") + + tag_ids = fields.Many2many("estate.property.tag", string="Tags") + + offer_ids = fields.One2many("estate.property.offer", "property_id") + + + + + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..2f5d4a78f7b --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,19 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer Model" + + + price = fields.Char(required = True) + status = fields.Selection( + selection=[ + ("accepted", "Accepted"), + ("refused", "Refused"), + ] + , copy= False + ) + + partner_id = fields.Many2one('res.partner' , "Partner") + property_id = fields.Many2one('estate.property' , "Property") \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..eb63b0005e1 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,9 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tag Model" + + + name = fields.Char(required = True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index ac245eddbb7..4c593ed42e4 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,3 +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 \ No newline at end of file +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 73c23876cae..33592a51bb1 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,25 +1,53 @@ - - - - """ - Estate Property - """ - + + + + Properties estate.property list,form + + + + Properties Types + estate.property.type + list,form + + + + Property Tags + estate.property.tag + 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 a0bc47fdc25..2dece87cf4d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -14,21 +14,25 @@ + - + - + + + + @@ -38,7 +42,24 @@ + + + + + + + + + + + + + + + + + @@ -50,6 +71,7 @@ + @@ -64,17 +86,17 @@ - - + - - + - - - + domain="['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]" /> + + + + From ac008cf46b24e9ee001a81f0daf41e65862c7f1b Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 17 Mar 2026 14:41:51 +0100 Subject: [PATCH 08/29] [IMP] estate: Chapter 8 --- estate/__init__.py | 2 +- estate/__manifest__.py | 4 +- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 82 ++++++++++++++------- estate/models/estate_property_offer.py | 38 ++++++++-- estate/models/estate_property_tag.py | 4 +- estate/models/estate_property_type.py | 4 +- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 28 +------ estate/views/estate_property_tag_views.xml | 15 ++++ estate/views/estate_property_type_views.xml | 16 ++++ estate/views/estate_property_views.xml | 13 +++- 12 files changed, 139 insertions(+), 71 deletions(-) create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml 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 e94b70e9ce8..bdce9287759 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,8 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_type_views.xml', 'views/estate_menus.xml' ] -} \ No newline at end of file +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 208a6195803..d3f24cb922b 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_type , estate_property_tag , estate_property_offer \ No newline at end of file +from . import estate_property, estate_property_type , estate_property_tag , estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 5a8815d5d92..8022850da28 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,62 +1,88 @@ -from odoo import models, fields +from odoo import models, fields, api +import logging +_logger = logging.getLogger(__name__) class EstateProperty(models.Model): - _name = "estate.property" - _description = "Estate Property model" + _name = 'estate.property' + _description = 'Estate Property' - name = fields.Char("Name", required=True) - description = fields.Text("description") + name = fields.Char(string="Name", required=True) + description = fields.Text(string='Description') postcode = fields.Char() date_availability = fields.Date( - string="Available From", + string='Available From', copy=False, - default=fields.Date.add(fields.Date.today(), days=90), + default=lambda self: fields.Date.add(fields.Date.today(), days=90) ) expected_price = fields.Float() + best_price = fields.Float(compute='_compute_best_price', readonly = True, store=True) selling_price = fields.Float(readonly=True, copy=False) - bedrooms = fields.Integer("Bedrooms", default=0) + bedrooms = fields.Integer(string='Bedrooms', default=0) + facades = fields.Integer(string='Facades') + garage = fields.Boolean(string='Has garage') - living_area = fields.Integer("Living_Area(sqm)") - facades = fields.Integer("Facades") - - garage = fields.Boolean("Has garage") - garden = fields.Boolean("Has garden") + living_area = fields.Float(string='Living_Area(sqm)') + garden = fields.Boolean(string='Has garden') + garden_area = fields.Float(string='Garden Area (sqm)') garden_orientation = fields.Selection( selection=[ - ("north", "North"), - ("south", "South"), - ("east", "East"), - ("west", "West"), + ('north', "North"), + ('south', "South"), + ('east', "East"), + ('west', "West"), ], string="Garden Orientation", ) - last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now) + total_area = fields.Float(compute='_compute_total_area', readonly = True, store=True) + + last_seen = fields.Datetime(string='Last Seen', default=fields.Datetime.now) active = fields.Boolean(default=True) state = fields.Selection( selection=[ - ("new", "New"), - ("offer_received", "Offer Received"), - ("offer_accepted", "Offer Accepted"), - ("sold", "Sold"), - ("cancelled", "Cancelled"), + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('cancelled', "Cancelled"), ], string="Status", ) - salesman_id = fields.Many2one("res.users", string="Salesman", default =lambda self: self.env.user ) - buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False ) - property_type_id = fields.Many2one("estate.property.type", string="Property Type") + salesman_id = fields.Many2one(comodel_name='res.users', string="Salesman", default =lambda self: self.env.user ) + buyer_id = fields.Many2one(comodel_name='res.partner', string="Buyer", copy=False ) + property_type_id = fields.Many2one(comodel_name='estate.property.type', string="Property Type") + tag_ids = fields.Many2many(comodel_name='estate.property.tag', string="Tags") + offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') + + + - tag_ids = fields.Many2many("estate.property.tag", string="Tags") + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped('price') + record.best_price = max(prices) if prices else 0.0 - offer_ids = fields.One2many("estate.property.offer", "property_id") + @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 = '' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 2f5d4a78f7b..7108a6e6d5f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,19 +1,43 @@ -from odoo import models, fields +from odoo import models, fields ,api class EstatePropertyType(models.Model): - _name = "estate.property.offer" - _description = "Estate Property Offer Model" + _name = 'estate.property.offer' + _description = 'Estate Property Offer' price = fields.Char(required = True) status = fields.Selection( selection=[ - ("accepted", "Accepted"), - ("refused", "Refused"), + ('accepted', "Accepted"), + ('refused', "Refused="), ] , copy= False ) - partner_id = fields.Many2one('res.partner' , "Partner") - property_id = fields.Many2one('estate.property' , "Property") \ No newline at end of file + validity = fields.Integer(string="Validity (days)", default=7) + date_deadline = fields.Date( + string="Deadline", + compute='_compute_date_deadline', + inverse='_inverse_date_deadline' + ) + + + + partner_id = fields.Many2one(comodel_name='res.partner' , string="Partner") + property_id = fields.Many2one(comodel_name='estate.property' , string="Property") + + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for record in self: + date_start = record.create_date.date() if record.create_date else fields.Date.today() + record.date_deadline = fields.Date.add(date_start, days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + date_start = record.create_date.date() if record.create_date else fields.Date.today() + if record.date_deadline: + record.validity = (record.date_deadline - date_start).days + else: + record.validity = 7 \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index eb63b0005e1..a1d4d4940a3 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -2,8 +2,8 @@ class EstatePropertyType(models.Model): - _name = "estate.property.tag" - _description = "Estate Property Tag Model" + _name = 'estate.property.tag' + _description = 'Estate Property Tag Model' name = fields.Char(required = True) \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 6aa1466febd..4088ec2971e 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -2,8 +2,8 @@ class EstatePropertyType(models.Model): - _name = "estate.property.type" - _description = "Estate Property Type Model" + _name = 'estate.property.type' + _description = 'Estate Property Type' name = fields.Char(required = True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 4c593ed42e4..49bca99cac8 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -2,4 +2,4 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 33592a51bb1..594e0cb0de3 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,32 +1,6 @@ - - - - - - Properties - estate.property - list,form - - - - - Properties Types - estate.property.type - list,form - - - - - Property Tags - estate.property.tag - list,form - - - + + + + + + + + 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..81b46236710 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,16 @@ + + + + + + + + Properties 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 2dece87cf4d..63d926f2aa2 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -24,6 +24,7 @@ + @@ -38,13 +39,15 @@ + + - + @@ -102,5 +105,13 @@ + + + Properties + estate.property + list,form + + +
\ No newline at end of file From ee5fa5d917b609850273153f631f0b999c958333 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 17 Mar 2026 15:29:15 +0100 Subject: [PATCH 09/29] [IMP] estate: Chapter 9 --- estate/models/estate_property.py | 17 +++++++++++++++++ estate/models/estate_property_offer.py | 20 +++++++++++++++++--- estate/views/estate_property_views.xml | 14 ++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8022850da28..b1283859253 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 import logging _logger = logging.getLogger(__name__) @@ -83,6 +84,22 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = '' + + def action_sell(self): + if self.state == 'cancelled': + raise UserError("A canceled property cannot be sold!") + elif self.state == 'sold': + raise UserError("The property is already sold!") + + self.state = 'sold' + return True + + def action_cancel(self): + if self.state == 'sold': + raise UserError("A Sold property cannot be canceled!") + self.state = 'cancelled' + return True + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7108a6e6d5f..d7711438319 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,5 @@ from odoo import models, fields ,api - +from odoo.exceptions import UserError class EstatePropertyType(models.Model): _name = 'estate.property.offer' @@ -10,7 +10,7 @@ class EstatePropertyType(models.Model): status = fields.Selection( selection=[ ('accepted', "Accepted"), - ('refused', "Refused="), + ('refused', "Refused"), ] , copy= False ) @@ -40,4 +40,18 @@ def _inverse_date_deadline(self): if record.date_deadline: record.validity = (record.date_deadline - date_start).days else: - record.validity = 7 \ No newline at end of file + record.validity = 7 + + def action_accept(self): + if "accepted" in self.property_id.offer_ids.mapped("status"): + raise UserError("An offer has already been accepted for this property!") + + self.status = "accepted" + self.property_id.selling_price = self.price + self.property_id.buyer_id = self.partner_id + + return True + + def action_refuse(self): + self.status = "refused" + return True \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 63d926f2aa2..12110528e84 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -8,6 +8,11 @@ estate.property
+ +
+

@@ -50,7 +55,16 @@ + + + + +

+ + +
+

+ +

+
+ + + + + + + + + + + + +
+ +
+ + + + + estate.property.type.list + estate.property.type + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 12110528e84..145ca071cf4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -10,9 +10,14 @@
-
+

@@ -22,10 +27,12 @@ - - - - + + + + @@ -45,14 +52,20 @@ - + + - - + + + @@ -60,11 +73,12 @@ +

+ + \ No newline at end of file diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js new file mode 100644 index 00000000000..2ec02daa6ed --- /dev/null +++ b/awesome_owl/static/src/counter/counter.js @@ -0,0 +1,22 @@ +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(); + } + } +} \ No newline at end of file diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml new file mode 100644 index 00000000000..0fb9f498365 --- /dev/null +++ b/awesome_owl/static/src/counter/counter.xml @@ -0,0 +1,12 @@ + + + +
+

+ Counter: + +

+ +
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js index 1aaea902b55..b78166ed913 100644 --- a/awesome_owl/static/src/main.js +++ b/awesome_owl/static/src/main.js @@ -4,7 +4,7 @@ import { Playground } from "./playground"; const config = { dev: true, - name: "Owl Tutorial" + name: "Owl Tutorial" }; // Mount the Playground component when the document.body is ready diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..e0a7e86a7cb 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,17 @@ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import { TodoList } from '@awesome_owl/todo/todo_list'; +import { Card } from '@awesome_owl/card/card'; +import { Counter } from '@awesome_owl/counter/counter'; +import { TodoItem } from '@awesome_owl/todo/todo_item'; export class Playground extends Component { static template = "awesome_owl.playground"; + static components = {TodoList, Card, Counter , TodoItem}; + setup(){ + this.state = useState({ sum: 0 }); + } + + incrementSum() { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..a16bdc68346 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,25 @@ - + -
- hello world +

+ Total Sum: + +

+
+
+ + + +
+
+ + + +
+
+ +
- -
+ \ No newline at end of file diff --git a/awesome_owl/static/src/todo/todo_item.js b/awesome_owl/static/src/todo/todo_item.js new file mode 100644 index 00000000000..099d7a7ee1f --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.js @@ -0,0 +1,28 @@ +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: {type: Function}, + removeItem: {type: Function} + }; + + onChange(){ + this.props.toggleState(this.props.todo.id) + } + + onClickRemoveItem(){ + this.props.removeItem(this.props.todo.id) + } + + +} \ No newline at end of file diff --git a/awesome_owl/static/src/todo/todo_item.xml b/awesome_owl/static/src/todo/todo_item.xml new file mode 100644 index 00000000000..25c2559fd39 --- /dev/null +++ b/awesome_owl/static/src/todo/todo_item.xml @@ -0,0 +1,15 @@ + + + +
+ + + + + + + + +
+
+
diff --git a/awesome_owl/static/src/todo/todo_list.js b/awesome_owl/static/src/todo/todo_list.js new file mode 100644 index 00000000000..43e1a887f8d --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.js @@ -0,0 +1,48 @@ +import { Component, useState, useRef, onMounted} from '@odoo/owl'; +import { TodoItem } from '@awesome_owl/todo/todo_item'; +import {useAutofocus} from '@awesome_owl/utils' + + +export class TodoList extends Component { + static template = 'awesome_owl.TodoList'; + static components = { TodoItem } + + setup() { + this.todos = useState([]) + this.Id = 1 + useAutofocus('input'); + } + + addTodo(ev) { + + if (ev.keyCode === 13) { + const description = ev.target.value.trim(); + + if (description !== '') { + this.todos.push({ + id: this.Id++ , + description: description, + isCompleted: false, + }); + + ev.target.value = ''; + } + } + } + + toggleTodo(todoId) { + const todo = this.todos.find((t) => t.id === todoId); + console.log(todo) + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + removeItemFromList(todoId) { + const index = this.todos.findIndex((t) => t.id === todoId); + + if (index >= 0) { + this.todos.splice(index, 1) + } + } +} \ No newline at end of file diff --git a/awesome_owl/static/src/todo/todo_list.xml b/awesome_owl/static/src/todo/todo_list.xml new file mode 100644 index 00000000000..8332111e67a --- /dev/null +++ b/awesome_owl/static/src/todo/todo_list.xml @@ -0,0 +1,14 @@ + + + +
+

My Tasks

+ +
+ + + +
+
+
+
\ No newline at end of file diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 00000000000..514f778878c --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,11 @@ +import { onMounted, useRef } from "@odoo/owl"; + +export function useAutofocus(refName) { + const ref = useRef(refName); + + onMounted(() => { + if (ref.el) { + ref.el.focus(); + } + }); +} \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 1c75ed053d5..5eeaec795bb 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,13 +9,14 @@ """, 'application': True, 'installable': True, + 'auto_install': True, 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', - 'views/estate_property_type_views.xml', - 'views/res_users_views.xml', + 'views/estate_property_type_views.xml', + 'views/res_users_views.xml', 'views/estate_menus.xml' ] } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ead3a0a67bc..07966645a7b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,30 +8,25 @@ class EstateProperty(models.Model): _name = 'estate.property' _description = 'Estate Property' - _order = "id desc" + _order = 'id desc' name = fields.Char(string="Name", required=True) - description = fields.Text(string='Description') - + description = fields.Text(string="Description") postcode = fields.Char() date_availability = fields.Date( - string='Available From', + string="Available From", copy=False, default=lambda self: fields.Date.add(fields.Date.today(), days=90) ) - expected_price = fields.Float() - best_price = fields.Float(compute='_compute_best_price', readonly = True, store=True) - + best_price = fields.Float(compute='_compute_best_price', readonly=True, store=True) selling_price = fields.Float(readonly=True, copy=False) - bedrooms = fields.Integer(string='Bedrooms', default=0) - facades = fields.Integer(string='Facades') - garage = fields.Boolean(string='Has garage') - - - living_area = fields.Float(string='Living_Area(sqm)') - garden = fields.Boolean(string='Has garden') - garden_area = fields.Float(string='Garden Area (sqm)') + bedrooms = fields.Integer(string="Bedrooms", default=0) + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Has garage") + living_area = fields.Float(string="Living_Area(sqm)") + garden = fields.Boolean(string="Has garden") + garden_area = fields.Float(string="Garden Area (sqm)") garden_orientation = fields.Selection( selection=[ ('north', "North"), @@ -41,9 +36,7 @@ class EstateProperty(models.Model): ], string="Garden Orientation", ) - total_area = fields.Float(compute='_compute_total_area', readonly = True, store=True) - last_seen = fields.Datetime(string='Last Seen', default=fields.Datetime.now) active = fields.Boolean(default=True) state = fields.Selection( @@ -58,23 +51,17 @@ class EstateProperty(models.Model): default='new' ) - - salesman_id = fields.Many2one(comodel_name='res.users', string="Salesman", default =lambda self: self.env.user ) - buyer_id = fields.Many2one(comodel_name='res.partner', string="Buyer", copy=False ) + salesman_id = fields.Many2one(comodel_name='res.users', string="Salesman", default=lambda self: self.env.user) + buyer_id = fields.Many2one(comodel_name='res.partner', string="Buyer", copy=False) property_type_id = fields.Many2one(comodel_name='estate.property.type', string="Property Type") tag_ids = fields.Many2many(comodel_name='estate.property.tag', string="Tags") offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') - - - _check_expected_prices = models.Constraint( 'CHECK(expected_price > 0)', "The expected price must be strictly positive.") _check_selling_price = models.Constraint( 'CHECK(selling_price > 0)', "The selling price must be strictly positive.") - - @api.constrains('expected_price', 'selling_price') def _check_selling_price(self): for record in self: @@ -85,17 +72,17 @@ def _check_selling_price(self): if float_compare(record.selling_price, limit_price, precision_digits=2) == -1: raise ValidationError( - ("The selling price cannot be lower than 90% of the expected price!" - "Check your offers or adjust the expected price.") + self.env._("The selling price cannot be lower than 90% of the expected price! Check your offers or adjust the expected price.") ) @api.constrains('offer_ids') def _check_creating_offer(self): - for record in self: - if record.state == 'sold' or record.state == 'cancelled' or record.state == 'offer_accepted' : - raise ValidationError( - ("the property is already Sold, Cancelled or Offer Accepted! You can not make a new offer.") - ) + properties = self.filtered(lambda r: r.state in ('sold', 'cancelled', 'offer_accepted')) + + if properties: + raise ValidationError( + self.env._("The property is already Sold, Cancelled, or Offer Accepted! You cannot make a new offer.") + ) @api.depends('living_area', 'garden_area') def _compute_total_area(self): @@ -128,14 +115,12 @@ def _onchange_garden(self): record.garden_area = 0 record.garden_orientation = False - @api.ondelete(at_uninstall=False) def _unlink_if_new_or_canceled(self): for record in self: if record.state not in ['new', 'cancelled']: raise UserError("You can only delete a property if its state is 'New' or 'Cancelled'.") - - + def action_sell(self): for record in self: if record.state == 'cancelled': @@ -155,4 +140,5 @@ def action_cancel(self): if record.state == 'sold': raise UserError("A Sold property cannot be canceled!") record.state = 'cancelled' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index a859590b2de..c5bf951b606 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,5 @@ from odoo import models, fields, api from odoo.exceptions import UserError -import pdb class EstatePropertyOffer(models.Model): @@ -40,36 +39,30 @@ class EstatePropertyOffer(models.Model): @api.depends('create_date', 'validity') def _compute_date_deadline(self): for record in self: - date_start = ( - record.create_date.date() if record.create_date else fields.Date.today() - ) + date_start = record.create_date.date() if record.create_date else fields.Date.today() record.date_deadline = fields.Date.add(date_start, days=record.validity) - @api.model_create_multi def create(self, vals_list): + property_ids = [v.get('property_id') for v in vals_list if v.get('property_id')] + properties = self.env['estate.property'].browse(property_ids) + property_map = {p.id: p for p in properties} for vals in vals_list: - property_id = vals.get('property_id') + prop_id = vals.get('property_id') + property_rec = property_map.get(prop_id) - if property_id: - property_record = self.env['estate.property'].browse(property_id) - - if property_record.offer_ids: - max_offer = max(property_record.mapped('offer_ids.price')) - if vals.get('price', 0) < max_offer: - raise UserError(f"You cannot make an offer lower ({vals.get('price', 0)}) than the current highest offer.") - - - return super().create(vals_list) - + if property_rec and property_rec.offer_ids: + max_offer = max(property_rec.offer_ids.mapped('price')) + if vals.get('price', 0) < max_offer: + raise UserError(f"You cannot make an offer lower ({vals.get('price', 0)}) than the current highest offer ({max_offer}).") + return super().create(vals_list) def _inverse_date_deadline(self): for record in self: - date_start = ( - record.create_date.date() if record.create_date else fields.Date.today() - ) + date_start = record.create_date.date() if record.create_date else fields.Date.today() + if record.date_deadline: record.validity = (record.date_deadline - date_start).days else: @@ -77,7 +70,7 @@ def _inverse_date_deadline(self): def action_accept(self): for record in self: - if 'accepted' in record.property_id.offer_ids.mapped('status'): + if 'offer_accepted' == record.property_id.state: raise UserError("An offer has already been accepted for this property!") record.status = 'accepted' @@ -90,7 +83,8 @@ def action_accept(self): def action_refuse(self): for record in self: if record.property_id.state == 'sold': - return False + raise UserError("The property has been already sold !") + record.status = 'refused' return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index d41e2f8eae8..77a974661e5 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,12 +1,11 @@ from odoo import models, fields -class EstatePropertyType(models.Model): +class EstatePropertyTag(models.Model): _name = 'estate.property.tag' _description = 'Estate Property Tag Model' _order = 'name' - name = fields.Char(string="Name", required=True) color = fields.Integer(string="Color") diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index edf0beff149..46473897ff8 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,22 +6,16 @@ class EstatePropertyType(models.Model): _description = 'Estate Property Type' _order = 'sequence, name' - name = fields.Char(required = True) - sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") - property_ids = fields.One2many(comodel_name='estate.property', inverse_name='property_type_id', string="Properties") - - offer_ids = fields.One2many(comodel_name='estate.property.offer' , inverse_name='property_type_id' , string="Offers" ) + offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_type_id', string="Offers") + offer_count = fields.Integer(compute="_compute_offer_count", string="Offer Count") _check_unique_tag = models.Constraint( 'UNIQUE(name)', "A property type name must be unique!" ) - - offer_count = fields.Integer(compute="_compute_offer_count", string="Offer Count") - @api.depends('offer_ids') def _compute_offer_count(self): for record in self: diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 647be628e22..cb7aeb3fc42 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -1,11 +1,12 @@ from odoo import models, fields + class ResUsers(models.Model): _inherit = "res.users" property_ids = fields.One2many( - comodel_name="estate.property", + comodel_name="estate.property", inverse_name="salesman_id", string="Properties", domain=[('state', 'in', ['new', 'offer_received'])] - ) \ No newline at end of file + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 49bca99cac8..a42bd95f417 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +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 +access_estate_property_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,access_estate_property_type_user,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,access_estate_property_offer_user,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 594e0cb0de3..dfbcbbe2f32 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,18 +2,13 @@ - - - - - - \ No newline at end of file + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 5c81c28e36b..5057bc0e4f0 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,8 +1,6 @@ - - estate.property.offer.list @@ -13,13 +11,9 @@ - - - - @@ -29,6 +23,5 @@ [('property_type_id', '=', active_id)] - - \ No newline at end of file + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 4667a055305..c173b5e2e21 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -2,7 +2,6 @@ - estate.property.tag.form estate.property.tag @@ -17,8 +16,6 @@ - - @@ -28,4 +25,4 @@ - \ No newline at end of file + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index fd945bfa7c2..23636550eb5 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -2,7 +2,6 @@ - estate.property.type.form estate.property.type @@ -11,7 +10,6 @@ -
-

@@ -43,7 +40,6 @@ - estate.property.type.list estate.property.type @@ -55,7 +51,6 @@ - @@ -64,6 +59,5 @@ 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 fa5cca467ce..c3a55aa3f4c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -2,7 +2,6 @@ - estate.property.form estate.property @@ -57,7 +56,6 @@ - @@ -103,8 +101,8 @@ + decoration-muted="state == 'sold'"> + @@ -122,6 +120,7 @@ estate.property.search estate.property + @@ -134,23 +133,16 @@ - + - estate.property.kanban estate.property - - - - - -
@@ -180,8 +172,6 @@ - - Properties @@ -195,6 +185,5 @@ - - \ No newline at end of file + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index a79410bfbc7..801ffd25ec4 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -1,17 +1,19 @@ + res.users.view.form.inherit.estate res.users - + - + + \ No newline at end of file diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py index f4c8fd6db6d..5e1963c9d2f 100644 --- a/estate_account/models/__init__.py +++ b/estate_account/models/__init__.py @@ -1 +1 @@ -from . import estate_property \ No newline at end of file +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 5559f991aa6..321554440f4 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,5 +1,5 @@ -from odoo import models , Command -import pdb +from odoo import models, Command + class EstateProperty(models.Model): _inherit = "estate.property" @@ -7,9 +7,9 @@ class EstateProperty(models.Model): def action_sell(self): for property_record in self: - + invoice_vals = { - 'name': property_record.name + ' Invoice' , + 'name': property_record.name + ' ' +'Invoice', 'partner_id': property_record.buyer_id.id, 'move_type': 'out_invoice', 'invoice_line_ids': [ @@ -30,7 +30,7 @@ def action_sell(self): }) ] } - + self.env['account.move'].create(invoice_vals) - return super().action_sell() \ No newline at end of file + return super().action_sell() From cb6ad2b9b29826a137aa7f3c64c0c5e7a24be58d Mon Sep 17 00:00:00 2001 From: kiro6 Date: Mon, 23 Mar 2026 13:31:29 +0100 Subject: [PATCH 17/29] [FIX] estate: Styling --- estate/models/estate_property.py | 29 ++++++++++++------------ estate/models/estate_property_offer.py | 8 +++---- estate/models/estate_property_type.py | 4 ++-- estate_account/models/estate_property.py | 4 ++-- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 07966645a7b..ba0d0d9c3d5 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -36,7 +36,7 @@ class EstateProperty(models.Model): ], string="Garden Orientation", ) - total_area = fields.Float(compute='_compute_total_area', readonly = True, store=True) + total_area = fields.Float(compute='_compute_total_area', readonly=True, store=True) last_seen = fields.Datetime(string='Last Seen', default=fields.Datetime.now) active = fields.Boolean(default=True) state = fields.Selection( @@ -57,9 +57,9 @@ class EstateProperty(models.Model): tag_ids = fields.Many2many(comodel_name='estate.property.tag', string="Tags") offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') - _check_expected_prices = models.Constraint( + _check_expected_prices = models.Constraint( 'CHECK(expected_price > 0)', "The expected price must be strictly positive.") - _check_selling_price = models.Constraint( + _check_selling_price = models.Constraint( 'CHECK(selling_price > 0)', "The selling price must be strictly positive.") @api.constrains('expected_price', 'selling_price') @@ -67,9 +67,9 @@ def _check_selling_price(self): for record in self: if float_is_zero(record.selling_price, precision_digits=2): continue - + limit_price = record.expected_price * 0.9 - + if float_compare(record.selling_price, limit_price, precision_digits=2) == -1: raise ValidationError( self.env._("The selling price cannot be lower than 90% of the expected price! Check your offers or adjust the expected price.") @@ -78,7 +78,7 @@ def _check_selling_price(self): @api.constrains('offer_ids') def _check_creating_offer(self): properties = self.filtered(lambda r: r.state in ('sold', 'cancelled', 'offer_accepted')) - + if properties: raise ValidationError( self.env._("The property is already Sold, Cancelled, or Offer Accepted! You cannot make a new offer.") @@ -88,7 +88,7 @@ def _check_creating_offer(self): def _compute_total_area(self): for record in self: record.total_area = record.living_area + record.garden_area - + @api.depends('offer_ids.price') def _compute_best_price(self): for record in self: @@ -104,7 +104,7 @@ def _onchange_offer_ids(self): record.state = 'offer_received' else: record.state = 'new' - + @api.onchange('garden') def _onchange_garden(self): for record in self: @@ -112,7 +112,7 @@ def _onchange_garden(self): record.garden_area = 10 record.garden_orientation = 'north' else: - record.garden_area = 0 + record.garden_area = 0 record.garden_orientation = False @api.ondelete(at_uninstall=False) @@ -120,16 +120,17 @@ def _unlink_if_new_or_canceled(self): for record in self: if record.state not in ['new', 'cancelled']: raise UserError("You can only delete a property if its state is 'New' or 'Cancelled'.") - + def action_sell(self): for record in self: if record.state == 'cancelled': raise UserError("A canceled property cannot be sold!") elif record.state == 'sold': - raise UserError("The property is already sold!") + raise UserError("The property is already sold!") record.state = 'sold' - for offer in record.offer_ids : + + for offer in record.offer_ids: if offer.status != 'accepted': offer.status = 'refused' @@ -138,7 +139,7 @@ def action_sell(self): def action_cancel(self): for record in self: if record.state == 'sold': - raise UserError("A Sold property cannot be canceled!") + raise UserError("A Sold property cannot be canceled!") record.state = 'cancelled' - + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index c5bf951b606..e4fb6b6ec4f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -31,7 +31,7 @@ class EstatePropertyOffer(models.Model): string="Property Type", store=True ) - + _check_price = models.Constraint( 'CHECK(price > 0', "The offer price must be strictly positive." ) @@ -51,7 +51,7 @@ def create(self, vals_list): for vals in vals_list: prop_id = vals.get('property_id') property_rec = property_map.get(prop_id) - + if property_rec and property_rec.offer_ids: max_offer = max(property_rec.offer_ids.mapped('price')) if vals.get('price', 0) < max_offer: @@ -62,7 +62,7 @@ def create(self, vals_list): def _inverse_date_deadline(self): for record in self: date_start = record.create_date.date() if record.create_date else fields.Date.today() - + if record.date_deadline: record.validity = (record.date_deadline - date_start).days else: @@ -70,7 +70,7 @@ def _inverse_date_deadline(self): def action_accept(self): for record in self: - if 'offer_accepted' == record.property_id.state: + if record.property_id.state == 'offer_accepted': raise UserError("An offer has already been accepted for this property!") record.status = 'accepted' diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 46473897ff8..82133dbff2b 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import models, fields ,api +from odoo import models, fields, api class EstatePropertyType(models.Model): @@ -6,7 +6,7 @@ class EstatePropertyType(models.Model): _description = 'Estate Property Type' _order = 'sequence, name' - name = fields.Char(required = True) + name = fields.Char(required=True) sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.") property_ids = fields.One2many(comodel_name='estate.property', inverse_name='property_type_id', string="Properties") offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_type_id', string="Offers") diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 321554440f4..ad517769dfc 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -7,9 +7,9 @@ class EstateProperty(models.Model): def action_sell(self): for property_record in self: - + invoice_vals = { - 'name': property_record.name + ' ' +'Invoice', + 'name': property_record.name + ' ' +'Invoice', 'partner_id': property_record.buyer_id.id, 'move_type': 'out_invoice', 'invoice_line_ids': [ From d89fc64da9ec53e3f32445e99693ded07b3a343d Mon Sep 17 00:00:00 2001 From: kiro6 Date: Mon, 23 Mar 2026 13:38:24 +0100 Subject: [PATCH 18/29] [FIX] estate: Styling --- estate/models/estate_property.py | 2 -- estate/models/estate_property_type.py | 2 +- estate_account/models/estate_property.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ba0d0d9c3d5..166c622e4af 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,7 @@ from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero -import logging -_logger = logging.getLogger(__name__) class EstateProperty(models.Model): _name = 'estate.property' diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 82133dbff2b..a92cb0b40eb 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api class EstatePropertyType(models.Model): diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index ad517769dfc..e3b17a04ead 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -9,7 +9,7 @@ def action_sell(self): for property_record in self: invoice_vals = { - 'name': property_record.name + ' ' +'Invoice', + 'name': property_record.name + ' ' + 'Invoice', 'partner_id': property_record.buyer_id.id, 'move_type': 'out_invoice', 'invoice_line_ids': [ From 6e03d888c8bc788fd2fcc125d165060bc4b66745 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 24 Mar 2026 15:50:47 +0100 Subject: [PATCH 19/29] [IMP] Owl: Chapter 2 --- awesome_dashboard/__manifest__.py | 42 ++++----- awesome_dashboard/static/src/dashboard.js | 8 -- awesome_dashboard/static/src/dashboard.xml | 8 -- .../static/src/dashboard/card/card.js | 19 ++++ .../static/src/dashboard/card/card.xml | 15 ++++ .../static/src/dashboard/dashboard.js | 63 ++++++++++++++ .../static/src/dashboard/dashboard.scss | 3 + .../static/src/dashboard/dashboard.xml | 24 +++++ .../dashboard_item/dashboard_item.js | 87 +++++++++++++++++++ .../dashboard_item/dashboard_item.xml | 11 +++ .../src/dashboard/pie_chart/pie_chart.js | 57 ++++++++++++ .../src/dashboard/pie_chart/pie_chart.xml | 7 ++ .../settings_dialog/settings_dialog.js | 31 +++++++ .../settings_dialog/settings_dialog.xml | 16 ++++ .../static/src/dashboard_action.js | 15 ++++ .../static/src/statistics_service.js | 30 +++++++ 16 files changed, 400 insertions(+), 36 deletions(-) delete mode 100644 awesome_dashboard/static/src/dashboard.js delete mode 100644 awesome_dashboard/static/src/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/card/card.js create mode 100644 awesome_dashboard/static/src/dashboard/card/card.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml create mode 100644 awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.js create mode 100644 awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.xml create mode 100644 awesome_dashboard/static/src/dashboard_action.js create mode 100644 awesome_dashboard/static/src/statistics_service.js diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..25acb14f3f4 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,30 +1,32 @@ # -*- coding: utf-8 -*- { - 'name': "Awesome Dashboard", - - 'summary': """ + "name": "Awesome Dashboard", + "summary": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'description': """ + "description": """ Starting module for "Discover the JS framework, chapter 2: Build a dashboard" """, - - 'author': "Odoo", - 'website': "https://www.odoo.com/", - 'category': 'Tutorials', - 'version': '0.1', - 'application': True, - 'installable': True, - 'depends': ['base', 'web', 'mail', 'crm'], - - 'data': [ - 'views/views.xml', + "author": "Odoo", + "website": "https://www.odoo.com/", + "category": "Tutorials", + "version": "0.1", + "application": True, + "installable": True, + "depends": ["base", "web", "mail", "crm"], + "data": [ + "views/views.xml", ], - 'assets': { - 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', + "assets": { + "web.assets_backend": [ + "awesome_dashboard/static/src/statistics_service.js", + "awesome_dashboard/static/src/dashboard_action.js", + ], + "awesome_dashboard.dashboard": [ + "awesome_dashboard/static/src/dashboard/**/*.js", + "awesome_dashboard/static/src/dashboard/**/*.xml", + "awesome_dashboard/static/src/dashboard/**/*.scss", ], }, - 'license': 'AGPL-3' + "license": "AGPL-3", } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index c4fb245621b..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index 1a2ac9a2fed..00000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/card/card.js b/awesome_dashboard/static/src/dashboard/card/card.js new file mode 100644 index 00000000000..ac4bec42f33 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/card/card.js @@ -0,0 +1,19 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../pie_chart/pie_chart"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: String, + value: [Number, String], + }; +} + +export class PieChartCard extends Component { + static components = { PieChart }; + static template = "awesome_dashboard.PieChartCard"; + static props = { + data: Object, + title: String, + }; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/card/card.xml b/awesome_dashboard/static/src/dashboard/card/card.xml new file mode 100644 index 00000000000..1e29aaecf68 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/card/card.xml @@ -0,0 +1,15 @@ + + +
+
+
+
+
+ + +
+
+ +
+
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 00000000000..76e3a8f735e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,63 @@ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from '@web/search/layout'; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { browser } from "@web/core/browser/browser"; +import { DashboardSettingsDialog } from "./settings_dialog/settings_dialog" + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem, DashboardSettingsDialog } + + setup() { + this.action = useService("action") + this.dialog = useService("dialog"); + + this.display = { + controlPanel: { + "layout-buttons": true + } + }; + + const statsService = useService("awesome_dashboard.statistics"); + this.statistics = useState(statsService.statistics); + + this.state = useState({ + items: this.getFilteredItems() + }); + + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: "Leads", + res_model: "crm.lead", + views: [ + [false, "list"], + [false, "form"], + ], + }); + } + + getFilteredItems() { + const allItems = registry.category("awesome_dashboard").getAll(); + const removedIds = JSON.parse(browser.localStorage.getItem("awesome_dashboard.removed_ids") || "[]"); + return allItems.filter(item => !removedIds.includes(item.id)); + } + + openConfiguration() { + this.dialog.add(DashboardSettingsDialog, { + onConfigSaved: () => { + this.state.items = this.getFilteredItems(); + } + }); + } +} + +registry.category("lazy_components").add("AwesomeDashboard", AwesomeDashboard); \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..3df35dce48e --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: #6a7b8c; +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..5ad583af11c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,24 @@ + + + + + + + + + +
Hellooooooooooooooooooo.
+
+ + + + + + + +
+
+
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..1b468139ac5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,87 @@ +import { Component } from "@odoo/owl"; +import { NumberCard, PieChartCard } from "./../card/card"; +import { registry } from "@web/core/registry"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { + size: { type: Number, optional: true }, + slots: { + type: Object, + shape: { + default: Object, + }, + }, + }; + static defaultProps = { + size: 1, + }; +} + +const dashboardRegistry = registry.category("awesome_dashboard"); + +dashboardRegistry.add("average_quantity", { + id: "average_quantity", + description: "Average amount of t-shirt", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Average quantity per order", + value: data.average_quantity, + }), +}); + +dashboardRegistry.add("average_time", { + id: "average_time", + description: "Average time", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Average Time", + value: data.average_time, + }), +}); + +dashboardRegistry.add("nb_cancelled_orders", { + id: "nb_cancelled_orders", + description: "Cancelled orders", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Cancelled Orders", + value: data.nb_cancelled_orders, + }), +}); + +dashboardRegistry.add("nb_new_orders", { + id: "nb_new_orders", + description: "New orders", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "New Orders", + value: data.nb_new_orders, + }), +}); + +dashboardRegistry.add("total_amount", { + id: "total_amount", + description: "Total amount", + Component: NumberCard, + size: 1, + props: (data) => ({ + title: "Total Amount", + value: `${data.total_amount} €`, + }), +}); + +dashboardRegistry.add("orders_by_size", { + id: "orders_by_size", + description: "Orders by size", + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: "T-Shirt Sizes", + data: data.orders_by_size, + }), +}); \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..9ec2c666dbf --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,11 @@ + + + +
+ + + +
+
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js new file mode 100644 index 00000000000..c3df2e861d6 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,57 @@ +import { Component, onWillStart, useRef, onMounted, onWillUnmount, useEffect } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + static props = { + data: { type: Object }, + label: { type: String, optional: true }, + }; + + setup() { + this.canvasRef = useRef("canvas"); + this.chart = null; + + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js"); + }); + + onMounted(() => { + this.renderChart(); + }); + + useEffect(() => { + if (this.chart) { + this.chart.data.labels = Object.keys(this.props.data); + this.chart.data.datasets[0].data = Object.values(this.props.data); + this.chart.update(); + } + }, + () => [this.props.data] + ); + + onWillUnmount(() => { + if (this.chart) { + this.chart.destroy(); + } + }); + + } + + renderChart() { + const config = { + type: 'pie', + data: { + labels: Object.keys(this.props.data), + datasets: [{ + label: this.props.label || 'T-Shirt Sizes', + data: Object.values(this.props.data), + backgroundColor: [ + '#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0' + ], + }] + }, + }; + this.chart = new Chart(this.canvasRef.el, config); + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..580f2e8179b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -0,0 +1,7 @@ + + +
+ +
+
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.js b/awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.js new file mode 100644 index 00000000000..40d53a3c82c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.js @@ -0,0 +1,31 @@ +import { Component, useState } from "@odoo/owl"; +import { Dialog } from "@web/core/dialog/dialog"; +import { registry } from "@web/core/registry"; +import { browser } from "@web/core/browser/browser"; + + +export class DashboardSettingsDialog extends Component { + static components = { Dialog }; + static template = "awesome_dashboard.SettingsDialog"; + + setup() { + const allItems = registry.category("awesome_dashboard").getAll(); + const removedIds = JSON.parse(browser.localStorage.getItem("awesome_dashboard.removed_ids") || "[]"); + + this.items = useState(allItems.map(item => ({ + ...item, + enabled: !removedIds.includes(item.id) + }))); + } + + onApply() { + const removedIds = this.items + .filter(item => !item.enabled) + .map(item => item.id); + + browser.localStorage.setItem("awesome_dashboard.removed_ids", JSON.stringify(removedIds)); + + this.props.onConfigSaved(); + this.props.close(); + } +} \ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.xml b/awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.xml new file mode 100644 index 00000000000..0bc756d5060 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/settings_dialog/settings_dialog.xml @@ -0,0 +1,16 @@ + + +
+ + + +
+ + + + +
+
\ No newline at end of file diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..0979e9cc8e5 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,15 @@ +import { Component, xml } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { LazyComponent } from "@web/core/assets"; + +export class AwesomeDashboardLoader extends Component { + static components = { LazyComponent }; + static template = xml` + + `; +} + +registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboardLoader); \ No newline at end of file diff --git a/awesome_dashboard/static/src/statistics_service.js b/awesome_dashboard/static/src/statistics_service.js new file mode 100644 index 00000000000..586c1899481 --- /dev/null +++ b/awesome_dashboard/static/src/statistics_service.js @@ -0,0 +1,30 @@ +import { registry } from "@web/core/registry"; +import { memoize } from "@web/core/utils/functions"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from '@odoo/owl'; + +export const statisticsService = { + start(env) { + const loadStatistics = memoize(() => rpc("/awesome_dashboard/statistics")); + + const statistics = reactive({}) + + + async function update(){ + const data = await rpc("/awesome_dashboard/statistics") + Object.assign(statistics, data); + console.log(statistics) + } + + update(); + + const interval = setInterval(update, 5000); + + + return { + statistics + }; + }, +}; + +registry.category("services").add("awesome_dashboard.statistics", statisticsService); \ No newline at end of file From 91f476b26af7b8f8213b9dc46a2ba6e32b567a7a Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 24 Mar 2026 15:59:07 +0100 Subject: [PATCH 20/29] [FIX] estate --- estate/models/estate_property_offer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e4fb6b6ec4f..7e97ea5882e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -33,7 +33,7 @@ class EstatePropertyOffer(models.Model): ) _check_price = models.Constraint( - 'CHECK(price > 0', "The offer price must be strictly positive." + 'CHECK(price > 0)', "The offer price must be strictly positive." ) @api.depends('create_date', 'validity') From 9c4cdd75e1477d4612276d2718a16cc50095dd4b Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 24 Mar 2026 16:06:16 +0100 Subject: [PATCH 21/29] [FIX] estate: estate.property.offer --- estate/models/estate_property_offer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7e97ea5882e..91c06c34dbe 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -7,7 +7,7 @@ class EstatePropertyOffer(models.Model): _description = 'Estate Property Offer' _order = 'price desc' - price = fields.Char(required=True) + price = fields.Float(required=True, string="Price") status = fields.Selection( selection=[ ('accepted', "Accepted"), From a97bded9f62dfc34fadde7101617a2bb5e4d4d49 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 24 Mar 2026 16:16:22 +0100 Subject: [PATCH 22/29] [FIX] estate: cleanup manifest & fix duplicate property_ids label --- estate/__manifest__.py | 1 + estate/models/res_users.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 5eeaec795bb..c425bc77a2c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,6 +3,7 @@ 'version': '1.0', 'depends': ['base'], 'author': "kiro", + 'license': 'LGPL-3', 'category': 'Category', 'description': """ Description text diff --git a/estate/models/res_users.py b/estate/models/res_users.py index cb7aeb3fc42..756830611bc 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -7,6 +7,6 @@ class ResUsers(models.Model): property_ids = fields.One2many( comodel_name="estate.property", inverse_name="salesman_id", - string="Properties", + string="Sales Properties", domain=[('state', 'in', ['new', 'offer_received'])] ) From 2d6e717d7783ea5e1047356a186a47885d82bbff Mon Sep 17 00:00:00 2001 From: kiro6 Date: Tue, 24 Mar 2026 16:24:48 +0100 Subject: [PATCH 23/29] [FIX] estate_account: add license --- estate_account/__manifest__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 73a2ff05df8..756173198b4 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -4,6 +4,7 @@ 'depends': ['estate', 'account'], 'author': "kiro", 'category': 'Category', + 'license': 'LGPL-3', 'description': """ Description text """, From 422f2b633350adcdfb61832d795367d2dae2201f Mon Sep 17 00:00:00 2001 From: kiro6 Date: Wed, 25 Mar 2026 11:23:14 +0100 Subject: [PATCH 24/29] [FIX] estate: dublicte view ID --- estate/views/estate_property_offer_views.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 5057bc0e4f0..675a7291405 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -2,7 +2,7 @@ - + estate.property.offer.list estate.property.offer From cf8481b546f4f78aed199a2413396e4be3aee300 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Thu, 26 Mar 2026 10:47:00 +0100 Subject: [PATCH 25/29] [IMP] Restrict access to data --- estate/__manifest__.py | 3 +- estate/models/estate_property.py | 1 + estate/security/ir.model.access.csv | 12 ++++--- estate/security/security.xml | 39 +++++++++++++++++++++ estate/views/estate_menus.xml | 2 +- estate/views/estate_property_type_views.xml | 1 - estate/views/estate_property_views.xml | 2 ++ estate_account/__manifest__.py | 2 +- estate_account/models/estate_property.py | 6 +++- 9 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 estate/security/security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index c425bc77a2c..622dbe5a8a1 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,7 +4,7 @@ 'depends': ['base'], 'author': "kiro", 'license': 'LGPL-3', - 'category': 'Category', + 'category': 'Real Estate/Brokerage', 'description': """ Description text """, @@ -12,6 +12,7 @@ 'installable': True, 'auto_install': True, 'data': [ + 'security/security.xml', 'security/ir.model.access.csv', 'views/estate_property_views.xml', 'views/estate_property_tag_views.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 166c622e4af..53a77526b55 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -54,6 +54,7 @@ class EstateProperty(models.Model): property_type_id = fields.Many2one(comodel_name='estate.property.type', string="Property Type") tag_ids = fields.Many2many(comodel_name='estate.property.tag', string="Tags") offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') + company_id = fields.Many2one(comodel_name='res.company', string="Company", required=True, default=lambda self: self.env.company) _check_expected_prices = models.Constraint( 'CHECK(expected_price > 0)', "The expected price must be strictly positive.") diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index a42bd95f417..2c346913e7b 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,5 +1,9 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_user,access_estate_property_user,model_estate_property,base.group_user,1,1,1,1 -access_estate_property_type_user,access_estate_property_type_user,model_estate_property_type,base.group_user,1,1,1,1 -access_estate_property_tag_user,access_estate_property_tag_user,model_estate_property_tag,base.group_user,1,1,1,1 -access_estate_property_offer_user,access_estate_property_offer_user,model_estate_property_offer,base.group_user,1,1,1,1 +access_estate_property_manager,access_estate_property_manager,model_estate_property,estate.estate_group_manager,1,1,1,0 +access_estate_property_type_manager,access_estate_property_type_manager,model_estate_property_type,estate.estate_group_manager,1,1,1,1 +access_estate_property_tag_manager,access_estate_property_tag_manager,model_estate_property_tag,estate.estate_group_manager,1,1,1,1 +access_estate_property_offer_manager,access_estate_property_offer_manager,model_estate_property_offer,estate.estate_group_manager,1,1,1,1 +access_estate_property_agent,access_estate_property_agent,model_estate_property,estate.estate_group_agent,1,1,1,0 +access_estate_property_type_agent,access_estate_property_type_agent,model_estate_property_type,estate.estate_group_agent,1,0,0,0 +access_estate_property_tag_agent,access_estate_property_tag_agent,model_estate_property_tag,estate.estate_group_agent,1,0,0,0 +access_estate_property_offer_agent,access_estate_property_offer_agent,model_estate_property_offer,estate.estate_group_agent,1,1,1,1 \ No newline at end of file diff --git a/estate/security/security.xml b/estate/security/security.xml new file mode 100644 index 00000000000..5f3a4205057 --- /dev/null +++ b/estate/security/security.xml @@ -0,0 +1,39 @@ + + + + Real Estate + + + + + Agent + + + + + Manager + + + + + + + Rule which limits agents to only being able to see or modify properties which have no salesperson, or for which they are the salesperson. + + ['|', ('salesman_id', '=', user.id), ('salesman_id', '=', False)] + + + + + Manager can see all properties + + [(1, '=', 1)] + + + + + Estate Property Multi-Company Rule + + [('company_id', 'in', company_ids)] + + \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index dfbcbbe2f32..ca3e5dd1a4a 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -10,7 +10,7 @@ id="estate_property_menu_action" action="estate_property_action" parent="estate_advertisements_menu" /> + name="Settings" parent="estate_menu_root" groups="estate.estate_group_manager" /> diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 23636550eb5..26dae411869 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -47,7 +47,6 @@ - diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c3a55aa3f4c..3d27b92b784 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -26,6 +26,7 @@ + @@ -104,6 +105,7 @@ decoration-muted="state == 'sold'"> + Date: Thu, 26 Mar 2026 16:12:50 +0100 Subject: [PATCH 26/29] [IMP] unit tests --- estate/models/estate_property.py | 12 ++++------- estate/models/estate_property_offer.py | 3 +++ estate/tests/__init__.py | 1 + estate/tests/test_estate_property.py | 29 ++++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 estate/tests/__init__.py create mode 100644 estate/tests/test_estate_property.py diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 53a77526b55..3fe47df9357 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -74,14 +74,6 @@ def _check_selling_price(self): self.env._("The selling price cannot be lower than 90% of the expected price! Check your offers or adjust the expected price.") ) - @api.constrains('offer_ids') - def _check_creating_offer(self): - properties = self.filtered(lambda r: r.state in ('sold', 'cancelled', 'offer_accepted')) - - if properties: - raise ValidationError( - self.env._("The property is already Sold, Cancelled, or Offer Accepted! You cannot make a new offer.") - ) @api.depends('living_area', 'garden_area') def _compute_total_area(self): @@ -127,6 +119,10 @@ def action_sell(self): elif record.state == 'sold': raise UserError("The property is already sold!") + accepted_offers = record.offer_ids.filtered(lambda offer: offer.status == 'accepted') + if not accepted_offers: + raise UserError("You cannot sell a property that has no accepted offers.") + record.state = 'sold' for offer in record.offer_ids: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 91c06c34dbe..5760ba5139a 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -52,6 +52,9 @@ def create(self, vals_list): prop_id = vals.get('property_id') property_rec = property_map.get(prop_id) + if property_rec.state in ('sold', 'offer_accepted', 'cancelled'): + raise UserError("You cannot make an offer for sold property.") + if property_rec and property_rec.offer_ids: max_offer = max(property_rec.offer_ids.mapped('price')) if vals.get('price', 0) < max_offer: diff --git a/estate/tests/__init__.py b/estate/tests/__init__.py new file mode 100644 index 00000000000..576617cccff --- /dev/null +++ b/estate/tests/__init__.py @@ -0,0 +1 @@ +from . import test_estate_property diff --git a/estate/tests/test_estate_property.py b/estate/tests/test_estate_property.py new file mode 100644 index 00000000000..9cd8d2ac435 --- /dev/null +++ b/estate/tests/test_estate_property.py @@ -0,0 +1,29 @@ +from odoo.tests.common import TransactionCase +from odoo.exceptions import UserError, ValidationError + + +class EstatePropertyCommon(TransactionCase): + @classmethod + def setUpClass(cls): + super(EstatePropertyCommon, cls).setUpClass() + + cls.property = cls.env['estate.property'].create({'name': "Test Property",'expected_price': 1.0}) + + def test_01_prevent_offer_for_sold_property(self): + """Test that creating an offer for a sold property raises a UserError.""" + + offer = self.env['estate.property.offer'].create({'property_id': self.property.id,'price': 2.0}) + offer.action_accept() + + self.property.action_sell() + + with self.assertRaises(UserError): + self.env['estate.property.offer'].create({'property_id': self.property.id,'price': 3.0}) + + def test_02_prevent_sell_no_accepted_offers(self): + """Test that selling a property without an accepted offer raises a UserError.""" + + self.property.state = 'new' + + with self.assertRaises(UserError): + self.property.action_sell() From b73ea4f1b9c92b848ce88822e62f1d8c0c305581 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Thu, 26 Mar 2026 16:27:04 +0100 Subject: [PATCH 27/29] [FIX] unit tests styling --- estate/models/estate_property.py | 1 - estate/tests/test_estate_property.py | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3fe47df9357..f09d7fd8703 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -74,7 +74,6 @@ def _check_selling_price(self): self.env._("The selling price cannot be lower than 90% of the expected price! Check your offers or adjust the expected price.") ) - @api.depends('living_area', 'garden_area') def _compute_total_area(self): for record in self: diff --git a/estate/tests/test_estate_property.py b/estate/tests/test_estate_property.py index 9cd8d2ac435..fbfdfe53124 100644 --- a/estate/tests/test_estate_property.py +++ b/estate/tests/test_estate_property.py @@ -1,24 +1,24 @@ from odoo.tests.common import TransactionCase -from odoo.exceptions import UserError, ValidationError +from odoo.exceptions import UserError class EstatePropertyCommon(TransactionCase): @classmethod def setUpClass(cls): - super(EstatePropertyCommon, cls).setUpClass() + super().setUpClass() - cls.property = cls.env['estate.property'].create({'name': "Test Property",'expected_price': 1.0}) + cls.property = cls.env['estate.property'].create({'name': "Test Property", 'expected_price': 1.0}) def test_01_prevent_offer_for_sold_property(self): """Test that creating an offer for a sold property raises a UserError.""" - offer = self.env['estate.property.offer'].create({'property_id': self.property.id,'price': 2.0}) + offer = self.env['estate.property.offer'].create({'property_id': self.property.id, 'price': 2.0}) offer.action_accept() self.property.action_sell() with self.assertRaises(UserError): - self.env['estate.property.offer'].create({'property_id': self.property.id,'price': 3.0}) + self.env['estate.property.offer'].create({'property_id': self.property.id, 'price': 3.0}) def test_02_prevent_sell_no_accepted_offers(self): """Test that selling a property without an accepted offer raises a UserError.""" From e7e1945b9d607c65e79be64ba02f1bdd6a0d65f3 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Fri, 27 Mar 2026 16:31:19 +0100 Subject: [PATCH 28/29] [IMP] Define module data --- estate/__manifest__.py | 8 +++++- estate/demo/estate.property.offer.xml | 23 +++++++++++++++++ estate/demo/estate.property.type.csv | 5 ++++ estate/demo/estate.property.xml | 34 ++++++++++++++++++++++++++ estate/models/estate_property.py | 6 ++--- estate/models/estate_property_offer.py | 2 +- estate/views/estate_property_views.xml | 5 ++-- 7 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 estate/demo/estate.property.offer.xml create mode 100644 estate/demo/estate.property.type.csv create mode 100644 estate/demo/estate.property.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 622dbe5a8a1..06869be3e83 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,11 +14,17 @@ 'data': [ 'security/security.xml', 'security/ir.model.access.csv', + 'views/estate_property_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/res_users_views.xml', - 'views/estate_menus.xml' + 'views/estate_menus.xml', + ], + 'demo': [ + 'demo/estate.property.type.csv', + 'demo/estate.property.xml', + 'demo/estate.property.offer.xml' ] } diff --git a/estate/demo/estate.property.offer.xml b/estate/demo/estate.property.offer.xml new file mode 100644 index 00000000000..a7bcaf0152a --- /dev/null +++ b/estate/demo/estate.property.offer.xml @@ -0,0 +1,23 @@ + + + + + + 10000 + 14 + + + + + + 1500000 + 14 + + + + + + 1500001 + 14 + + \ No newline at end of file diff --git a/estate/demo/estate.property.type.csv b/estate/demo/estate.property.type.csv new file mode 100644 index 00000000000..cc7793d4965 --- /dev/null +++ b/estate/demo/estate.property.type.csv @@ -0,0 +1,5 @@ +"id","name","sequence" +"property_type_1","Residential",1 +"property_type_2","Commercial",2 +"property_type_3","Industrial",3 +"property_type_4","Land",4 \ No newline at end of file diff --git a/estate/demo/estate.property.xml b/estate/demo/estate.property.xml new file mode 100644 index 00000000000..40dc19780b9 --- /dev/null +++ b/estate/demo/estate.property.xml @@ -0,0 +1,34 @@ + + + + Big Villa + new + + A nice and big villa + 12345 + 2020-02-02 + 1600000 + 6 + 100 + 4 + True + True + 100000 + south + + + + Trailer home + cancelled + + Home in a trailer park + 54321 + 1970-01-01 + 100000 + 120000 + 1 + 10 + 4 + False + + \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index f09d7fd8703..a8ef91a06a9 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -56,10 +56,8 @@ class EstateProperty(models.Model): offer_ids = fields.One2many(comodel_name='estate.property.offer', inverse_name='property_id') company_id = fields.Many2one(comodel_name='res.company', string="Company", required=True, default=lambda self: self.env.company) - _check_expected_prices = models.Constraint( - 'CHECK(expected_price > 0)', "The expected price must be strictly positive.") - _check_selling_price = models.Constraint( - 'CHECK(selling_price > 0)', "The selling price must be strictly positive.") + _check_expected_prices_postive = models.Constraint(definition='CHECK(expected_price > 0)', message="The expected price must be strictly positive.") + _check_selling_price_postive = models.Constraint(definition='CHECK(selling_price > 0)', message="The selling price must be strictly positive.") @api.constrains('expected_price', 'selling_price') def _check_selling_price(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5760ba5139a..8a6f3fc424e 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -44,7 +44,7 @@ def _compute_date_deadline(self): @api.model_create_multi def create(self, vals_list): - property_ids = [v.get('property_id') for v in vals_list if v.get('property_id')] + property_ids = [ v.get('property_id') for v in vals_list if v.get('property_id') ] properties = self.env['estate.property'].browse(property_ids) property_map = {p.id: p for p in properties} diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 3d27b92b784..94d7f7fee63 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -106,6 +106,7 @@ + Expected Price:
-
Best Price: Best Price:
-
Selling +
Selling Price:
From a02e4d7149df26263cd0bcf34385fee864648009 Mon Sep 17 00:00:00 2001 From: kiro6 Date: Fri, 27 Mar 2026 16:51:13 +0100 Subject: [PATCH 29/29] [FIX] Define module data --- estate/__manifest__.py | 2 +- estate/models/estate_property_offer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 06869be3e83..77b3b333aac 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,7 +14,7 @@ 'data': [ 'security/security.xml', 'security/ir.model.access.csv', - + 'views/estate_property_views.xml', 'views/estate_property_tag_views.xml', 'views/estate_property_offer_views.xml', diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8a6f3fc424e..5760ba5139a 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -44,7 +44,7 @@ def _compute_date_deadline(self): @api.model_create_multi def create(self, vals_list): - property_ids = [ v.get('property_id') for v in vals_list if v.get('property_id') ] + property_ids = [v.get('property_id') for v in vals_list if v.get('property_id')] properties = self.env['estate.property'].browse(property_ids) property_map = {p.id: p for p in properties}