From 29c0511784e8ec46557591ddd5b7f9da3656750e Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Wed, 23 Apr 2025 06:17:04 +0200 Subject: [PATCH 01/18] [ADD] estate: added a new estate module We have added a module as an application with a model a some basic properties like (name, description, expected price ...etc) We have added security rules for read, write, create and delete actions on this model We have added menu items and UI interaction to access a form view of the records we create and we also added attributes to some properties of the model like (copy, default, readonly, required) --- estate/__init__.py | 1 + estate/__manifest__.py | 10 ++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 45 ++++++++++++++++++++++++++ estate/security/ir.model.access.csv | 2 ++ estate/views/estate_menus.xml | 7 ++++ estate/views/estate_property_views.xml | 15 +++++++++ 7 files changed, 81 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/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ 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 new file mode 100644 index 00000000000..b5c9829e1e4 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': 'Estate', + 'depends': ['base'], + 'application': True, + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' + ] +} \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..599d75b64b7 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,45 @@ +from odoo import models, fields +from datetime import datetime +from dateutil.relativedelta import relativedelta + +def _default_date_availability(): + return fields.Date.today() + relativedelta(months=3) + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property Information" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date( + copy=False, + default=_default_date_availability() + ) + state = fields.Selection( + string='Status', + required=True, + copy=False, + default='new', + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], + ) + 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='Garden Orientation', + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + help="Orientation of the garden" + ) + active = fields.Boolean(default=True) \ 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..ab63520e22b --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,estate.model_estate_property,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 new file mode 100644 index 00000000000..f57bb515500 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..ab538048204 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,15 @@ + + + + + Properties + estate.property + list,form + + + Create a new property listing! + + + + + \ No newline at end of file From ede556ed3b28fb287310d79aae06b9556731cb17 Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Wed, 23 Apr 2025 06:31:20 +0200 Subject: [PATCH 02/18] [REF] estate: refactored some code Changed the individual definition of menuitems to nested menu items for better readability --- estate/views/estate_menus.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index f57bb515500..b5243efd24b 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,7 @@ - - - - - + + + + + From 84e5068665e50adfd9978d3e7cfd003f8f3a55c2 Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Wed, 23 Apr 2025 12:43:15 +0200 Subject: [PATCH 03/18] [REF] estate: added flake8 linting Added linting with flake8 and fixed some linting warnings --- estate/__init__.py | 3 ++- estate/__manifest__.py | 5 ++--- estate/models/__init__.py | 3 ++- estate/models/estate_property.py | 5 +++-- estate/views/estate_property_views.xml | 17 +++++++++++++++++ 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..64a574e1d46 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1,2 @@ -from . import models \ No newline at end of file +from . import models +assert models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b5c9829e1e4..c9f635e0550 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,5 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_menus.xml' - ] -} \ No newline at end of file + 'views/estate_menus.xml'] +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..2412cac060a 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,2 @@ -from . import estate_property \ No newline at end of file +from . import estate_property +assert estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 599d75b64b7..3a4cf7445fc 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,10 +1,11 @@ from odoo import models, fields -from datetime import datetime from dateutil.relativedelta import relativedelta + def _default_date_availability(): return fields.Date.today() + relativedelta(months=3) + class EstateProperty(models.Model): _name = "estate.property" _description = "Real Estate Property Information" @@ -42,4 +43,4 @@ class EstateProperty(models.Model): selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Orientation of the garden" ) - active = fields.Boolean(default=True) \ No newline at end of file + active = fields.Boolean(default=True) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ab538048204..0905a5216d1 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -11,5 +11,22 @@
+ Create a new property listing! +
+ Create a new property tag +
+ Create a new property type +
Create a new property listing! @@ -16,7 +17,7 @@ estate.property.list estate.property - + @@ -25,8 +26,8 @@ - - + + @@ -37,8 +38,9 @@ - - + + + @@ -46,8 +48,8 @@ - - + + @@ -66,15 +68,14 @@ - - + + - - + @@ -99,9 +100,9 @@ - + - + From df29c68083d53e55a87e1a3b133b0e04837b6e8c Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Fri, 25 Apr 2025 14:42:14 +0200 Subject: [PATCH 11/18] [FIX] estate: fixed data import order Fixed views imports in the data field in the manifest file --- estate/__manifest__.py | 2 +- estate/views/estate_property_offers_views.xml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 77b141ba6e0..52beca08015 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,8 +6,8 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_offers_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tags_views.xml', - 'views/estate_property_offers_views.xml', 'views/estate_menus.xml'] } diff --git a/estate/views/estate_property_offers_views.xml b/estate/views/estate_property_offers_views.xml index 4d86ceae88d..f2a553741c7 100644 --- a/estate/views/estate_property_offers_views.xml +++ b/estate/views/estate_property_offers_views.xml @@ -4,6 +4,7 @@ Property Offers + [('property_type_id', '=', active_id)] estate.property.offer list,form From 0d101b04f972e9ad26f4f56050df3140bc0a9a06 Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Fri, 25 Apr 2025 15:42:43 +0200 Subject: [PATCH 12/18] [IMP] estate: chapter12 - inheritance features - Overrided CRUD methods for property deletion and offer creation logic (don't allow deletion of ongoing properties, and don't create offers lower than the existing ones) - Add properties field to res.user model and add a view to see properties from the user list view --- estate/__manifest__.py | 1 + estate/models/__init__.py | 2 ++ estate/models/estate_property.py | 6 ++++++ estate/models/estate_property_offer.py | 21 +++++++++++++++++++++ estate/models/res_users.py | 11 +++++++++++ estate/views/res_users_views.xml | 16 ++++++++++++++++ 6 files changed, 57 insertions(+) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_users_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 52beca08015..b6287355929 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,5 +9,6 @@ 'views/estate_property_offers_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tags_views.xml', + 'views/res_users_views.xml', 'views/estate_menus.xml'] } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 920403d20c7..7cf7209fcee 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,8 +2,10 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users assert estate_property assert estate_property_type assert estate_property_tag assert estate_property_offer +assert res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8678989bf24..53f1296a51e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -79,6 +79,12 @@ def action_cancel(self): record.state = 'cancelled' return True + @api.ondelete(at_uninstall=False) + def _check_unlink_state(self): + for record in self: + if record.state not in ('new', 'cancelled'): + raise UserError("You cannot delete a property that is not in 'New' or 'Cancelled' state.") + @api.constrains('expected_price', 'selling_price') def _check_selling_price(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 1171a7661ee..7e291645981 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -2,6 +2,7 @@ from dateutil.relativedelta import relativedelta from odoo.exceptions import UserError +from odoo.tools.float_utils import float_compare class EstatePropertyOffer(models.Model): @@ -28,6 +29,26 @@ class EstatePropertyOffer(models.Model): ('check_price', 'CHECK(price > 0)', 'The offer price must be strictly positive.') ] + @api.model_create_multi + def create(self, vals): + for val in vals: + property_id = val.get('property_id') + price = val.get('price') + + prop = self.env['estate.property'].browse(property_id) + + existing_offer_prices = prop.offers_ids.mapped('price') + if existing_offer_prices: + max_existing_offer = max(existing_offer_prices) + if float_compare(price, max_existing_offer, precision_digits=2) < 0: + raise UserError("Cannot create an offer with a price lower than an existing offer") + + if prop.state == 'new': + prop.write({'state': 'offer_received'}) + + new_offer = super(EstatePropertyOffer, self).create(vals) + return new_offer + def action_accept(self): for record in self: accepted_offers = record.property_id.offers_ids.filtered(lambda o: o.status == 'accepted') diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..600464c3d27 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,11 @@ +from odoo import fields, models + +class ResUsers(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many( + comodel_name='estate.property', + inverse_name='salesperson_id', + string="Real Estate Properties", + domain=[('state', 'in', ('new', 'offer_received'))] + ) diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..e23526afecb --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,16 @@ + + + + res.users.form.inherit.estate + res.users + + + + + + + + + + + \ No newline at end of file From 4878ba53b9354403809779c58e0b12192fc104d4 Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Fri, 25 Apr 2025 23:12:15 +0200 Subject: [PATCH 13/18] [IMP] estate: chapter13 - invoice on sold property - Added a new link module estate_account responsible for creating an invoice when the sold button is created - Overriden the action_set_sold method to add the creation of the invoice in it --- estate/models/estate_property_offer.py | 2 +- estate/models/res_users.py | 1 + estate_account/__init__.py | 2 ++ estate_account/__manifest__.py | 12 +++++++ estate_account/models/__init__.py | 3 ++ estate_account/models/estate_property.py | 45 ++++++++++++++++++++++++ 6 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7e291645981..5d0a76bbbd3 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -46,7 +46,7 @@ def create(self, vals): if prop.state == 'new': prop.write({'state': 'offer_received'}) - new_offer = super(EstatePropertyOffer, self).create(vals) + new_offer = super().create(vals) return new_offer def action_accept(self): diff --git a/estate/models/res_users.py b/estate/models/res_users.py index 600464c3d27..96cb1afc7c5 100644 --- a/estate/models/res_users.py +++ b/estate/models/res_users.py @@ -1,5 +1,6 @@ from odoo import fields, models + class ResUsers(models.Model): _inherit = 'res.users' diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 00000000000..64a574e1d46 --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1,2 @@ +from . import models +assert models diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 00000000000..4b6b1c3455a --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': "Real Estate Accounting Link", + 'depends': [ + 'estate', + 'account', + ], + 'data': [ + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} \ No newline at end of file diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..a126cc40c5b --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1,3 @@ +from . import estate_property + +assert estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..a5f255f8420 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,45 @@ +from odoo import models, Command +from odoo.exceptions import UserError + + +class EstateProperty(models.Model): + _inherit = "estate.property" + + def action_set_sold(self): + res = super().action_set_sold() + + if not self: + return res + + for record in self: + if not record.buyer_id: + raise UserError("Cannot create invoice: Buyer is not set for property") + + if record.selling_price <= 0: + raise UserError("Cannot create invoice: Selling price must be positive for property") + + commission = record.selling_price * 0.06 + admin_fees = 100.00 + + invoice_line_commands = [ + Command.create({ + 'name': '6% Commission', + 'quantity': 1, + 'price_unit': commission, + }), + Command.create({ + 'name': 'Administrative Fees', + 'quantity': 1, + 'price_unit': admin_fees, + }), + ] + + invoice_vals = { + 'partner_id': record.buyer_id.id, + 'move_type': 'out_invoice', + 'invoice_line_ids': invoice_line_commands, + } + + self.env['account.move'].create(invoice_vals) + + return res From d27033691fff30172bdd07596294aa14fafb1f4c Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Mon, 28 Apr 2025 16:02:12 +0200 Subject: [PATCH 14/18] [IMP] estate: chapter14 - Add kanban view Added kanban view grouped by property type for properties --- estate/models/estate_property.py | 6 ++++- estate/views/estate_property_views.xml | 34 +++++++++++++++++++++++++- estate_account/__manifest__.py | 2 +- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 53f1296a51e..08fde83bd65 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -18,7 +18,7 @@ class EstateProperty(models.Model): description = fields.Text() sequence = fields.Integer('Sequence', default=10) property_tag_ids = fields.Many2many("estate.property.tag") - property_type_id = fields.Many2one("estate.property.type", string="Property Type") + property_type_id = fields.Many2one("estate.property.type", string="Property Type", group_expand="group_by_empty") postcode = fields.Char() date_availability = fields.Date( copy=False, @@ -65,6 +65,10 @@ class EstateProperty(models.Model): ('check_selling_price', 'CHECK(selling_price >= 0)', 'The selling price must be positive.'), ] + @api.model + def group_by_empty(self, types, domain): + return types.search([]) # this is equivalent to return self.env['estate.property.type'].search([]) + def action_set_sold(self): for record in self: if record.state == 'cancelled': diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 6180a05afcb..5df06e41802 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_available': True, 'search_default_current': True} @@ -32,6 +32,38 @@ + + estate.property.kanban + estate.property + + + + + + + + + + Expected Price: + + + + + Best Offer: + + + Selling Price: + + + + + + + + + + + estate.property.view.form estate.property diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py index 4b6b1c3455a..d0bb2361f67 100644 --- a/estate_account/__manifest__.py +++ b/estate_account/__manifest__.py @@ -9,4 +9,4 @@ 'installable': True, 'application': False, 'license': 'LGPL-3', -} \ No newline at end of file +} From 77d0bd8af5156c59c187d21424ee5a92aaf5f706 Mon Sep 17 00:00:00 2001 From: abeg-odoo Date: Tue, 29 Apr 2025 17:28:40 +0200 Subject: [PATCH 15/18] [IMP] estate: chapter15 - corrected code after review - Removed un necessary assert (for the unused imports in the __init__ files) - Renamed looping variables' names for better readability (property instead of record, and offer instead of record) - Added auto_install property to True in the estate_account link module (used to auto install dependencies when we install estate_account) --- estate/__manifest__.py | 6 +++ estate/models/__init__.py | 6 --- estate/models/estate_property.py | 50 ++++++++++++------------ estate/models/estate_property_offer.py | 48 ++++++++++++----------- estate/views/estate_property_views.xml | 2 +- estate/views/res_users_views.xml | 2 +- estate_account/__manifest__.py | 7 ++++ estate_account/models/estate_property.py | 16 ++++---- 8 files changed, 73 insertions(+), 64 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b6287355929..1c52ad2b569 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,11 @@ { 'name': 'Estate', + 'summary': "Module to manage properties", + 'description': """ +This module is used to manage any type of properties and also to manage the selling pipeline for each property + """, + 'author': 'Odoo', + 'version': '1.0', 'depends': ['base'], 'application': True, 'license': 'LGPL-3', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 7cf7209fcee..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -3,9 +3,3 @@ from . import estate_property_tag from . import estate_property_offer from . import res_users - -assert estate_property -assert estate_property_type -assert estate_property_tag -assert estate_property_offer -assert res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 08fde83bd65..1bae9b5c633 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,11 +1,11 @@ -from odoo import api, models, fields -from odoo.exceptions import UserError, ValidationError from dateutil.relativedelta import relativedelta +from odoo import _, api, models, fields +from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero -def _default_date_availability(): +def _default_date_availability(_): return fields.Date.today() + relativedelta(months=3) @@ -22,7 +22,7 @@ class EstateProperty(models.Model): postcode = fields.Char() date_availability = fields.Date( copy=False, - default=_default_date_availability() + default=_default_date_availability ) state = fields.Selection( string='Status', @@ -70,46 +70,46 @@ def group_by_empty(self, types, domain): return types.search([]) # this is equivalent to return self.env['estate.property.type'].search([]) def action_set_sold(self): - for record in self: - if record.state == 'cancelled': - raise UserError("Canceled properties cannot be set as sold.") - record.state = 'sold' + for property in self: + if property.state == 'cancelled': + raise UserError(_("Canceled properties cannot be set as sold.")) + property.state = 'sold' return True def action_cancel(self): - for record in self: - if record.state == 'sold': - raise UserError("Sold properties cannot be canceled.") - record.state = 'cancelled' + for property in self: + if property.state == 'sold': + raise UserError(_("Sold properties cannot be canceled.")) + property.state = 'cancelled' return True @api.ondelete(at_uninstall=False) def _check_unlink_state(self): - for record in self: - if record.state not in ('new', 'cancelled'): - raise UserError("You cannot delete a property that is not in 'New' or 'Cancelled' state.") + for property in self: + if property.state not in ('new', 'cancelled'): + raise UserError(_("You cannot delete a property that is not in 'New' or 'Cancelled' state.")) @api.constrains('expected_price', 'selling_price') def _check_selling_price(self): - for record in self: - if not float_is_zero(record.expected_price, precision_digits=2): - accepted_offers = record.offers_ids.filtered(lambda o: o.status == 'accepted') + for property in self: + if not float_is_zero(property.expected_price, precision_digits=2): + accepted_offers = property.offers_ids.filtered(lambda o: o.status == 'accepted') if accepted_offers: - if float_compare(record.selling_price, record.expected_price * 0.90, precision_digits=2) == -1: + if float_compare(property.selling_price, property.expected_price * 0.90, precision_digits=2) == -1: raise ValidationError("Selling price cannot be lower than 90 percent of expected price") @api.depends('living_area', 'garden_area') def _compute_total_area(self): - for record in self: - record.total_area = record.living_area + record.garden_area + for property in self: + property.total_area = property.living_area + property.garden_area @api.depends('offers_ids.price') def _compute_best_price(self): - for record in self: - if record.offers_ids: - record.best_price = max(record.offers_ids.mapped('price')) + for property in self: + if property.offers_ids: + property.best_price = max(property.offers_ids.mapped('price')) else: - record.best_price = 0.0 + property.best_price = 0.0 @api.onchange('garden') def _onchange_garden(self): diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5d0a76bbbd3..12a4c6b5739 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import api, models, fields +from odoo import _, api, models, fields from dateutil.relativedelta import relativedelta from odoo.exceptions import UserError @@ -30,10 +30,12 @@ class EstatePropertyOffer(models.Model): ] @api.model_create_multi - def create(self, vals): - for val in vals: + def create(self, vals_list): + for val in vals_list: property_id = val.get('property_id') - price = val.get('price') + if not property_id: + continue + price = val.get('price', 0) prop = self.env['estate.property'].browse(property_id) @@ -41,43 +43,43 @@ def create(self, vals): if existing_offer_prices: max_existing_offer = max(existing_offer_prices) if float_compare(price, max_existing_offer, precision_digits=2) < 0: - raise UserError("Cannot create an offer with a price lower than an existing offer") + raise UserError(_("Cannot create an offer with a price lower than an existing offer")) if prop.state == 'new': prop.write({'state': 'offer_received'}) - new_offer = super().create(vals) + new_offer = super().create(vals_list) return new_offer def action_accept(self): - for record in self: - accepted_offers = record.property_id.offers_ids.filtered(lambda o: o.status == 'accepted') + for offer in self: + accepted_offers = offer.property_id.offers_ids.filtered(lambda o: o.status == 'accepted') if accepted_offers: - raise UserError("Another offer has already been accepted for this property.") - record.status = 'accepted' - record.property_id.state = 'offer_accepted' - record.property_id.selling_price = record.price - record.property_id.buyer_id = record.partner_id + raise UserError(_("Another offer has already been accepted for this property.")) + offer.status = 'accepted' + offer.property_id.state = 'offer_accepted' + offer.property_id.selling_price = offer.price + offer.property_id.buyer_id = offer.partner_id return True def action_refuse(self): - for record in self: - record.status = 'refused' + for offer in self: + offer.status = 'refused' return True @api.depends('create_date', 'validity') def _compute_date_deadline(self): - for record in self: - if record.create_date: - base_date = record.create_date.date() + for offer in self: + if offer.create_date: + base_date = offer.create_date.date() else: base_date = fields.Date.today() - record.date_deadline = base_date + relativedelta(days=record.validity) + offer.date_deadline = base_date + relativedelta(days=offer.validity) def _inverse_date_deadline(self): - for record in self: - if record.create_date: - base_date = record.create_date.date() + for offer in self: + if offer.create_date: + base_date = offer.create_date.date() else: base_date = fields.Date.today() - record.validity = (record.date_deadline - base_date).days + offer.validity = (offer.date_deadline - base_date).days diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5df06e41802..9b3740af78f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -136,7 +136,7 @@ - + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index e23526afecb..52ae487ee2c 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -13,4 +13,4 @@
@@ -32,6 +32,38 @@
+ +
- +
+