From cdc392eaf3bd9d6398294464c5d9d0a2aaa9f0d8 Mon Sep 17 00:00:00 2001 From: josv Date: Mon, 19 May 2025 16:44:48 +0200 Subject: [PATCH 1/8] [ADD] base: create a new module for real estate management --- estate/__init__.py | 1 + estate/__manifest__.py | 17 ++++++++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 40 +++++++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/view/estate_menus.xml | 7 +++++ estate/view/estate_property_views.xml | 24 ++++++++++++++++ 7 files changed, 92 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py create mode 100644 estate/security/ir.model.access.csv create mode 100644 estate/view/estate_menus.xml create mode 100644 estate/view/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..7ad91d4c71b --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,17 @@ +{ + 'name': 'Real estate', + 'version': '0.1', + 'depends': ['base'], + 'author': 'odoo SA', + 'category': 'Finance', + 'description': """ + Empty real estate app for tutorial purposes + """, + 'application': 'True', + 'data': [ + 'security/ir.model.access.csv', + 'view/estate_property_views.xml', + 'view/estate_menus.xml', + ], + 'license': 'LGPL-3', +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..e52bc5682ff --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,40 @@ +from dateutil.relativedelta import relativedelta +from odoo import fields, models + + +class Estate(models.Model): + _name = 'estate.property' + _description = 'It allows to manage your properties' + + name = fields.Char(required=True, default='Unknown') + last_seen = fields.Datetime('Last Seen', default=fields.Datetime.now) + description = fields.Char(required=True) + postcode = fields.Char() + date_availability = fields.Date( + default=fields.Date.today() + relativedelta(months=3), copy=False + ) + active = fields.Boolean(default=True) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + string='Orientation', + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + ) + state = fields.Selection( + string='State', + selection=[ + ('new', 'New'), + ('offer received', 'Offer Received'), + ('offer accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + default='new', + readonly=True, + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..d9d6ba57cc5 --- /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 diff --git a/estate/view/estate_menus.xml b/estate/view/estate_menus.xml new file mode 100644 index 00000000000..464fbd77871 --- /dev/null +++ b/estate/view/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml new file mode 100644 index 00000000000..9c02ff61f3c --- /dev/null +++ b/estate/view/estate_property_views.xml @@ -0,0 +1,24 @@ + + + + action_test_action + estate.property + list,form + + + + estate.main.list + estate.property + + + + + + + + + + + + + From b6990b9fe8fb862469689ff8df9736ae3f373b09 Mon Sep 17 00:00:00 2001 From: josv-odoo Date: Tue, 20 May 2025 17:05:59 +0200 Subject: [PATCH 2/8] [IMP] Estate: add the property types --- estate/.gitignore | 2 + estate/__manifest__.py | 1 + estate/models/__init__.py | 2 +- estate/models/estate_property.py | 1 + estate/models/estate_property_type.py | 9 +++ estate/security/ir.model.access.csv | 1 + estate/view/estate_menus.xml | 3 + estate/view/estate_property_type_views.xml | 17 +++++ estate/view/estate_property_views.xml | 72 +++++++++++++++++++--- 9 files changed, 100 insertions(+), 8 deletions(-) create mode 100644 estate/.gitignore create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/view/estate_property_type_views.xml diff --git a/estate/.gitignore b/estate/.gitignore new file mode 100644 index 00000000000..052f26423f3 --- /dev/null +++ b/estate/.gitignore @@ -0,0 +1,2 @@ +#Untracked files +pyproject.toml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 7ad91d4c71b..b893e30435e 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,6 +11,7 @@ 'data': [ 'security/ir.model.access.csv', 'view/estate_property_views.xml', + 'view/estate_property_type_views.xml', 'view/estate_menus.xml', ], 'license': 'LGPL-3', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..76e779e73b0 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property +from . import estate_property, estate_property_type diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e52bc5682ff..db316cafbdd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -7,6 +7,7 @@ class Estate(models.Model): _description = 'It allows to manage your properties' name = fields.Char(required=True, default='Unknown') + property_type_id = fields.Many2one('estate.property.type') last_seen = fields.Datetime('Last Seen', default=fields.Datetime.now) description = fields.Char(required=True) postcode = fields.Char() diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..4a0aa0bfb35 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,9 @@ +from dateutil.relativedelta import relativedelta +from odoo import fields, models + + +class EstateType(models.Model): + _name = 'estate.property.type' + _description = 'It allows to create a new property type' + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index d9d6ba57cc5..0527da2a522 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 +estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 diff --git a/estate/view/estate_menus.xml b/estate/view/estate_menus.xml index 464fbd77871..cd9310b82ba 100644 --- a/estate/view/estate_menus.xml +++ b/estate/view/estate_menus.xml @@ -3,5 +3,8 @@ + + + diff --git a/estate/view/estate_property_type_views.xml b/estate/view/estate_property_type_views.xml new file mode 100644 index 00000000000..f8907447d2c --- /dev/null +++ b/estate/view/estate_property_type_views.xml @@ -0,0 +1,17 @@ + + + estate.type.main.list + estate.property.type + + + + + + + + + property_type_action + estate.property.type + list + + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 9c02ff61f3c..4bdd7ccf9e0 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -1,11 +1,4 @@ - - - action_test_action - estate.property - list,form - - estate.main.list estate.property @@ -21,4 +14,69 @@ + + + estate.form + estate.property + +
+ +

+ +

+
+
+ + + + +
+
+ + + + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ + + estate.view.search + estate.property + + + + + + + + + + + + + + + property_action + estate.property + list,form +
From c5e168522de8afdb526a112bab77b951bc55f5ac Mon Sep 17 00:00:00 2001 From: josv-odoo Date: Wed, 21 May 2025 13:11:59 +0200 Subject: [PATCH 3/8] [IMP] Estate: Improve relation between model\n create new models: estate_property_? offer, tag and type\n create one2many, many2one and many2many fields --- estate/__manifest__.py | 2 + estate/models/__init__.py | 2 +- estate/models/estate_property.py | 31 +++++++++++--- estate/models/estate_property_offer.py | 36 ++++++++++++++++ estate/models/estate_property_tag.py | 8 ++++ estate/models/estate_property_type.py | 1 - estate/security/ir.model.access.csv | 2 + estate/view/estate_menus.xml | 4 +- estate/view/estate_property_offer_views.xml | 46 +++++++++++++++++++++ estate/view/estate_property_tag_views.xml | 17 ++++++++ estate/view/estate_property_views.xml | 18 ++++++-- 11 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/view/estate_property_offer_views.xml create mode 100644 estate/view/estate_property_tag_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b893e30435e..63a85b0edcd 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,8 @@ 'security/ir.model.access.csv', 'view/estate_property_views.xml', 'view/estate_property_type_views.xml', + 'view/estate_property_tag_views.xml', + 'view/estate_property_offer_views.xml', 'view/estate_menus.xml', ], 'license': 'LGPL-3', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 76e779e73b0..ba5f3418d75 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_type +from . import estate_property, estate_property_tag, estate_property_type, estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index db316cafbdd..59d9ad41cf0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from dateutil.relativedelta import relativedelta -from odoo import fields, models +from odoo import api, fields, models class Estate(models.Model): @@ -8,25 +8,29 @@ class Estate(models.Model): name = fields.Char(required=True, default='Unknown') property_type_id = fields.Many2one('estate.property.type') + tag_ids = fields.Many2many('estate.property.tag') last_seen = fields.Datetime('Last Seen', default=fields.Datetime.now) description = fields.Char(required=True) postcode = fields.Char() - date_availability = fields.Date( - default=fields.Date.today() + relativedelta(months=3), copy=False - ) + date_availability = fields.Date(default=fields.Date.today() + relativedelta(months=3), copy=False) active = fields.Boolean(default=True) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) + best_offer = fields.Float(compute='_compute_best_offer') + sales_person_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user) + offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer') + buyer_id = fields.Many2one('res.partner', string='Customer') bedrooms = fields.Integer(default=2) - living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() garden_orientation = fields.Selection( string='Orientation', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], ) + living_area = fields.Integer() + garden_area = fields.Integer() + total_area = fields.Float(compute='_compute_total_area') state = fields.Selection( string='State', selection=[ @@ -39,3 +43,18 @@ class Estate(models.Model): default='new', readonly=True, ) + + @api.depends('living_area', 'garden_area') + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends('offer_ids') + def _compute_best_offer(self): + for record in self: + all_price = record.offer_ids.mapped('price') + if all_price: + record.best_offer = max(all_price) + else: + record.best_offer = 0 + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..f3cd016142c --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,36 @@ +from dateutil.relativedelta import relativedelta +from odoo import api, fields, models + + +class EstateOffer(models.Model): + _name = 'estate.property.offer' + _description = 'It allows to create a new property offer' + + price = fields.Float(required=True) + validity = fields.Integer() + date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date') + create_date = fields.Date(default=fields.Date.today(), required=True) + partner_id = fields.Many2one('res.partner', string='Customer') + property_id = fields.Many2one('estate.property', string='Property') + status= fields.Selection( + string='State', + selection=[ + ('pending', 'Pending'), + ('accepted', 'Accepted'), + ('refused', 'Refused'), + ], + default='pending', + ) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = record.create_date + relativedelta(days=record.validity) + else: + record.date_deadline = fields.Date.today() + relativedelta(days=record.validity) + + + def _inverse_date(self): + for record in self: + record.validity = (record.date_deadline - fields.Date.today()).days diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..af9ba5d47af --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstateTags(models.Model): + _name = 'estate.property.tag' + _description = 'It allows to create a new property tag' + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 4a0aa0bfb35..a484b2e56ec 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,3 @@ -from dateutil.relativedelta import relativedelta from odoo import fields, models diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0527da2a522..c4e7d89e491 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 estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1 +estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1 +estate.access_estate_property_offer,access_estate_property_offer,estate.model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/view/estate_menus.xml b/estate/view/estate_menus.xml index cd9310b82ba..87b39a249c7 100644 --- a/estate/view/estate_menus.xml +++ b/estate/view/estate_menus.xml @@ -3,8 +3,10 @@ - + + + diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml new file mode 100644 index 00000000000..4076d660ce7 --- /dev/null +++ b/estate/view/estate_property_offer_views.xml @@ -0,0 +1,46 @@ + + + estate.offer.main.list + estate.property.offer + + + + + + + + + + + + estate.offer.form + estate.property.offer + +
+ +

+ +

+
+
+ + + + + + + + +
+
+
+
+
+
+ + + property_offer_action + estate.property.offer + list,form + +
diff --git a/estate/view/estate_property_tag_views.xml b/estate/view/estate_property_tag_views.xml new file mode 100644 index 00000000000..bf7b133622f --- /dev/null +++ b/estate/view/estate_property_tag_views.xml @@ -0,0 +1,17 @@ + + + estate.tag.main.list + estate.property.tag + + + + + + + + + property_tag_action + estate.property.tag + list + + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 4bdd7ccf9e0..e18de3cc21b 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -22,8 +22,9 @@

- +

+
@@ -35,6 +36,7 @@ +
@@ -43,15 +45,25 @@ - - + + + + + + + + + + + +
From bc6fa2c5aebfe36541668c1fc8aa2b382160ced2 Mon Sep 17 00:00:00 2001 From: josv-odoo Date: Thu, 22 May 2025 13:40:06 +0200 Subject: [PATCH 4/8] [IMP] Estate: Improve security, added actions in the properties form, Improve the aesthetics. . Security: prohibit: duplicate tags and negative prices Actions: added visuals for properties state, linked status of offer to state of properties, added action to accept/refuse offer, added action to sold/cancel properties. Aesthetics : Added color to tags, hidden sold/cancelled/accepted/refused button (properties view), added status as a status bar on top (properties view), added properties (in properties type view) --- estate/models/estate_property.py | 38 ++++++++++++++++++++- estate/models/estate_property_offer.py | 33 +++++++++++++++--- estate/models/estate_property_tag.py | 4 +++ estate/models/estate_property_type.py | 3 ++ estate/view/estate_property_offer_views.xml | 2 ++ estate/view/estate_property_tag_views.xml | 1 + estate/view/estate_property_type_views.xml | 30 ++++++++++++++-- estate/view/estate_property_views.xml | 15 +++++--- 8 files changed, 114 insertions(+), 12 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 59d9ad41cf0..bec12227ce2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,11 @@ from dateutil.relativedelta import relativedelta -from odoo import api, fields, models +from odoo import api, exceptions, fields, models class Estate(models.Model): _name = 'estate.property' _description = 'It allows to manage your properties' + _order = 'id desc' name = fields.Char(required=True, default='Unknown') property_type_id = fields.Many2one('estate.property.type') @@ -44,11 +45,26 @@ class Estate(models.Model): readonly=True, ) + _sql_constraints = [ + ('expected_price', 'CHECK(expected_price >= 0 )', 'A price should always be possitive'), + ('selling_price', 'CHECK(selling_price >= 0 )', 'A price should always be possitive'), + ] + @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.onchange('offer_ids') + def _onchange_status(self): + for record in self: + if record.state not in ['offer accepted', 'sold', 'cancelled']: + all_status = record.offer_ids.mapped('status') + new_state = 'new' + if all_status: + new_state = 'offer received' + record.state = new_state + @api.depends('offer_ids') def _compute_best_offer(self): for record in self: @@ -58,3 +74,23 @@ def _compute_best_offer(self): else: record.best_offer = 0 + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = '' + + def action_property_sold(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError('property already cancelled') + record.state = 'sold' + + def action_property_cancelled(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError('property already sold') + record.state = 'cancelled' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index f3cd016142c..001411cc4de 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,10 +1,11 @@ from dateutil.relativedelta import relativedelta -from odoo import api, fields, models +from odoo import api, exceptions, fields, models class EstateOffer(models.Model): _name = 'estate.property.offer' _description = 'It allows to create a new property offer' + _order = 'price desc' price = fields.Float(required=True) validity = fields.Integer() @@ -12,7 +13,7 @@ class EstateOffer(models.Model): create_date = fields.Date(default=fields.Date.today(), required=True) partner_id = fields.Many2one('res.partner', string='Customer') property_id = fields.Many2one('estate.property', string='Property') - status= fields.Selection( + status = fields.Selection( string='State', selection=[ ('pending', 'Pending'), @@ -22,15 +23,39 @@ class EstateOffer(models.Model): default='pending', ) + _sql_constraints = [('price', 'CHECK(price >= 0 )', 'A price should always be possitive')] + @api.depends('create_date', 'validity') def _compute_date_deadline(self): for record in self: if record.create_date: record.date_deadline = record.create_date + relativedelta(days=record.validity) else: - record.date_deadline = fields.Date.today() + relativedelta(days=record.validity) - + record.date_deadline = fields.Date.today() + relativedelta(days=record.validity) def _inverse_date(self): for record in self: record.validity = (record.date_deadline - fields.Date.today()).days + + def action_accept_offer(self): + for record in self: + if record.property_id.state not in ['sold', 'cancelled']: + for offer in record.property_id.offer_ids: + offer.status = 'refused' + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.state = 'offer accepted' + else: + raise exceptions.UserError('property already cancelled') + return True + + def action_refuse_offer(self): + for record in self: + if record.property_id.state not in ['sold', 'cancelled']: + if record.status == 'accepted': + record.property_id.selling_price = 0 + record.property_id.state = 'offer received' + else: + raise exceptions.UserError('property already sold or cancelled') + record.status = 'refused' + return True diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index af9ba5d47af..668fcd63cf6 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -4,5 +4,9 @@ class EstateTags(models.Model): _name = 'estate.property.tag' _description = 'It allows to create a new property tag' + _order = 'name desc' name = fields.Char(required=True) + color = fields.Integer(default=0) + + _sql_constraints = [('unique_tag_by_estate_property', 'UNIQUE(name)', 'Only one name by tag')] diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index a484b2e56ec..84fac96b24d 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -4,5 +4,8 @@ class EstateType(models.Model): _name = 'estate.property.type' _description = 'It allows to create a new property type' + _order = 'sequence desc' name = fields.Char(required=True) + sequence = fields.Integer('Sequence', default=1, help='Used to order properties types. Lower is better.') + properties_ids = fields.One2many('estate.property', 'property_type_id') diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml index 4076d660ce7..ac71caaa496 100644 --- a/estate/view/estate_property_offer_views.xml +++ b/estate/view/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + +

diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index beb84ab62b5..571df75ee47 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -3,7 +3,7 @@ estate.main.list estate.property - + @@ -23,7 +23,7 @@

@@ -81,6 +81,7 @@ + @@ -95,5 +96,6 @@ property_action estate.property list,form + {'search_default_available': True} From 0a3a38acd362e4ba2fa68cab1ba3c6bd49071d5c Mon Sep 17 00:00:00 2001 From: josv-odoo Date: Tue, 27 May 2025 15:09:29 +0200 Subject: [PATCH 7/8] [REL] Estate: add kanban view in property and link to invoicing. Kanban: - new kanban record in the xml and add kanban as standard view Link with invoicing: New module called estate_account - model called estate_sold_inherited that inherit from account to implement on "action_property_sold" (when a property is marked as sold) to create an invoice with the following lines: * property price * commission for the sale of property * Administrative fees --- estate/__manifest__.py | 1 + estate/models/__init__.py | 2 +- estate/models/estate_property.py | 9 ++++- estate/models/estate_property_offer.py | 7 ++++ estate/models/user_property.py | 9 +++++ estate/view/estate_property_type_views.xml | 12 +++--- estate/view/estate_property_views.xml | 22 ++++++++++- estate/view/user_property_views.xml | 16 ++++++++ estate_account/__init__.py | 1 + estate_account/__manifest__.py | 12 ++++++ estate_account/models/__init__.py | 1 + .../models/estate_sold_inherited.py | 38 +++++++++++++++++++ 12 files changed, 119 insertions(+), 11 deletions(-) create mode 100644 estate/models/user_property.py create mode 100644 estate/view/user_property_views.xml create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_sold_inherited.py diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 63a85b0edcd..1f833dc484a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -15,6 +15,7 @@ 'view/estate_property_tag_views.xml', 'view/estate_property_offer_views.xml', 'view/estate_menus.xml', + 'view/user_property_views.xml' ], 'license': 'LGPL-3', } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index ba5f3418d75..c0ef8d7922c 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_tag, estate_property_type, estate_property_offer +from . import estate_property, estate_property_tag, estate_property_type, estate_property_offer, user_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 26e05daa452..f42fd7b7bdd 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -43,7 +43,7 @@ class Estate(models.Model): ('cancelled', 'Cancelled'), ], readonly=True, - default='new' + default='new', ) _sql_constraints = [ @@ -61,7 +61,7 @@ def _onchange_offer_ids(self): if self.state == 'new': if len(self.offer_ids): self.state = 'offer received' - self._origin.state = 'offer received' #error, didn't found the source, fix, I'm a bugfixer + self._origin.state = 'offer received' # error, didn't found the source, fix, I'm a bugfixer @api.depends('offer_ids') def _compute_best_offer(self): @@ -92,3 +92,8 @@ def action_property_cancelled(self): if record.state == 'sold': raise UserError(self.env._('property already sold')) record.state = 'cancelled' + + @api.ondelete(at_uninstall=False) + def _unlink_for_state(self): + if self.state not in ['new', 'cancelled']: + raise UserError('You cannot delete an property that as is not New or cancelled') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 2b5e44cd132..b15cefe70e1 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -58,3 +58,10 @@ def action_refuse_offer(self): raise UserError(self.env._('property already sold')) record.status = 'refused' return True + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals['price'] < self.env['estate.property'].browse(vals['property_id']).best_offer: + raise UserError('The price is lower than the best-offer') + return super(EstateOffer, self).create(vals_list) diff --git a/estate/models/user_property.py b/estate/models/user_property.py new file mode 100644 index 00000000000..ef81d7a4818 --- /dev/null +++ b/estate/models/user_property.py @@ -0,0 +1,9 @@ +from odoo import fields, models + + +class UserProperty(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many( + 'estate.property', 'sales_person_id', domain=[('state', 'in', ['new', 'offer received'])] + ) diff --git a/estate/view/estate_property_type_views.xml b/estate/view/estate_property_type_views.xml index 41b145a8eb1..ea8e385e92b 100644 --- a/estate/view/estate_property_type_views.xml +++ b/estate/view/estate_property_type_views.xml @@ -1,10 +1,4 @@ - - Offers - estate.property.offer - list - [('property_type_id', '=', active_id)] - estate.type.main.list estate.property.type @@ -59,4 +53,10 @@ + + Offers + estate.property.offer + list + [('property_type_id', '=', active_id)] + diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 571df75ee47..767c1095210 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -15,6 +15,25 @@ + + estate.main.kanban + estate.property + + + +
+ + + + + + +
+
+
+
+
+ estate.form estate.property @@ -91,11 +110,10 @@
- property_action estate.property - list,form + kanban,list,form {'search_default_available': True} diff --git a/estate/view/user_property_views.xml b/estate/view/user_property_views.xml new file mode 100644 index 00000000000..c970964a4ac --- /dev/null +++ b/estate/view/user_property_views.xml @@ -0,0 +1,16 @@ + + + res.users.view.form.inherit.property.offer + res.users + + + + + + + + + + + + diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..54bed152adb --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': 'EstateAccount', + 'version': '0.1', + 'depends': ['estate', 'account'], + 'author': 'odoo SA', + 'category': 'Finance', + 'description': """ + Link module to link estate and invoicing + """, + 'application': 'False', + 'license': 'LGPL-3', +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..6ccb0e0820c --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_sold_inherited diff --git a/estate_account/models/estate_sold_inherited.py b/estate_account/models/estate_sold_inherited.py new file mode 100644 index 00000000000..0174206974c --- /dev/null +++ b/estate_account/models/estate_sold_inherited.py @@ -0,0 +1,38 @@ +from odoo import Command, models + + +class EstateSoldInherited(models.Model): + _inherit = 'estate.property' + + def action_property_sold(self): + result = super().action_property_sold() + self.env['account.move'].create( + { + 'partner_id': self.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': [ + Command.create( + { + 'name': f'The property {self.name}', + 'quantity': 1, + 'price_unit': self.selling_price, + } + ), + Command.create( + { + 'name': f'Commission for property {self.name}', + 'quantity': 1, + 'price_unit': self.selling_price * 0.06, + } + ), + Command.create( + { + 'name': 'Administrative fees', + 'quantity': 1, + 'price_unit': 100.0, + } + ), + ], + } + ) + return result From 07bdb3cd892a2487123fd4242e03c6add68d7b63 Mon Sep 17 00:00:00 2001 From: josv-odoo Date: Wed, 28 May 2025 09:28:47 +0200 Subject: [PATCH 8/8] [FIX] Estate: relocate action before it's call. - move estate.estate_property_type_offer_action - checkStyle: super(class,self) -> super() - erase '_' at the end of compute function --- estate/models/estate_property_offer.py | 2 +- estate/models/estate_property_type.py | 4 ++-- estate/view/estate_property_offer_views.xml | 2 +- estate/view/estate_property_tag_views.xml | 2 +- estate/view/estate_property_type_views.xml | 15 ++++++++------- estate/view/estate_property_views.xml | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index b15cefe70e1..4415be2ec42 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -64,4 +64,4 @@ def create(self, vals_list): for vals in vals_list: if vals['price'] < self.env['estate.property'].browse(vals['property_id']).best_offer: raise UserError('The price is lower than the best-offer') - return super(EstateOffer, self).create(vals_list) + return super().create(vals_list) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 83644bdd6a1..9898d6df066 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -10,9 +10,9 @@ class EstateType(models.Model): sequence = fields.Integer('Sequence', default=1, help='Used to order properties types. Lower is better.') properties_ids = fields.One2many('estate.property', 'property_type_id') offer_ids = fields.One2many('estate.property.offer', 'property_type_id') - offer_count = fields.Integer(compute='_compute_offer_stats_') + offer_count = fields.Integer(compute='_compute_offer_stats') @api.depends('offer_ids') - def _compute_offer_stats_(self): + def _compute_offer_stats(self): for record in self: record.offer_count = len(record.offer_ids) or 0 diff --git a/estate/view/estate_property_offer_views.xml b/estate/view/estate_property_offer_views.xml index 6cd66bb1a94..f15407178cc 100644 --- a/estate/view/estate_property_offer_views.xml +++ b/estate/view/estate_property_offer_views.xml @@ -42,7 +42,7 @@ - property_offer_action + Offers estate.property.offer list,form diff --git a/estate/view/estate_property_tag_views.xml b/estate/view/estate_property_tag_views.xml index 305a3c03455..9bcb8690cc9 100644 --- a/estate/view/estate_property_tag_views.xml +++ b/estate/view/estate_property_tag_views.xml @@ -11,7 +11,7 @@ - property_tag_action + Tags estate.property.tag list diff --git a/estate/view/estate_property_type_views.xml b/estate/view/estate_property_type_views.xml index ea8e385e92b..d1b03b42470 100644 --- a/estate/view/estate_property_type_views.xml +++ b/estate/view/estate_property_type_views.xml @@ -11,11 +11,18 @@ - property_type_action + Types estate.property.type list,form + + Offers + estate.property.offer + list + [('property_type_id', '=', active_id)] + + estate.property.type.form estate.property.type @@ -53,10 +60,4 @@ - - Offers - estate.property.offer - list - [('property_type_id', '=', active_id)] - diff --git a/estate/view/estate_property_views.xml b/estate/view/estate_property_views.xml index 767c1095210..ca8c3afd433 100644 --- a/estate/view/estate_property_views.xml +++ b/estate/view/estate_property_views.xml @@ -111,7 +111,7 @@ - property_action + Property estate.property kanban,list,form {'search_default_available': True}