From 8a82939b979bf2a355de6a13614eab2ea6500424 Mon Sep 17 00:00:00 2001 From: prbo-odoo Date: Thu, 3 Jul 2025 12:03:44 +0530 Subject: [PATCH 1/7] [ADD] real_estate: completed chapters 1-3 with module and basic model setup --- real_estate/__init__.py | 1 + real_estate/__manifest__.py | 10 ++++++++++ real_estate/models/__init__.py | 1 + real_estate/models/estate_property.py | 24 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 real_estate/__init__.py create mode 100644 real_estate/__manifest__.py create mode 100644 real_estate/models/__init__.py create mode 100644 real_estate/models/estate_property.py diff --git a/real_estate/__init__.py b/real_estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/real_estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/real_estate/__manifest__.py b/real_estate/__manifest__.py new file mode 100644 index 00000000000..10d4960e07a --- /dev/null +++ b/real_estate/__manifest__.py @@ -0,0 +1,10 @@ +{ + 'name': "RealEstate", + 'depends': ['base'], + 'sequence': 1, + 'application': True, + 'category': 'Real Estate', + 'version': '1.0', + 'author': "prbo" + +} \ No newline at end of file diff --git a/real_estate/models/__init__.py b/real_estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/real_estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/real_estate/models/estate_property.py b/real_estate/models/estate_property.py new file mode 100644 index 00000000000..a30dd193f35 --- /dev/null +++ b/real_estate/models/estate_property.py @@ -0,0 +1,24 @@ +from odoo import fields, models + +class estate_property(models.Model): + _name = "estate.property" + _description = "Test Model" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(copy=False) + 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="Direction of the garden" + ) \ No newline at end of file From c48844bcb2355fed74d3469f218f07d69fdfad51 Mon Sep 17 00:00:00 2001 From: prbo-odoo Date: Fri, 4 Jul 2025 11:04:43 +0530 Subject: [PATCH 2/7] [ADD] real_estate: add security rules, UI menu and basic views for model This commit covers Chapters 4, 5, and 6 of the server framework tutorial. It includes: - Adding security access rules for the basic model to manage user permissions. - Creating the first user interface by adding a menu item and action to navigate to the model records. - Defining the basic tree (list) view and form view for the model to enable record visualization and editing. --- real_estate/__manifest__.py | 9 +- real_estate/data/ir.model.access.csv | 2 + real_estate/models/estate_property.py | 25 ++++- real_estate/security/ir.model.access.csv | 2 + real_estate/views/estate_menus.xml | 15 +++ real_estate/views/estate_property_views.xml | 100 ++++++++++++++++++++ 6 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 real_estate/data/ir.model.access.csv create mode 100644 real_estate/security/ir.model.access.csv create mode 100644 real_estate/views/estate_menus.xml create mode 100644 real_estate/views/estate_property_views.xml diff --git a/real_estate/__manifest__.py b/real_estate/__manifest__.py index 10d4960e07a..18e57546169 100644 --- a/real_estate/__manifest__.py +++ b/real_estate/__manifest__.py @@ -2,9 +2,14 @@ 'name': "RealEstate", 'depends': ['base'], 'sequence': 1, + 'license': 'LGPL-3', 'application': True, 'category': 'Real Estate', 'version': '1.0', - 'author': "prbo" - + 'author': "prbo", + 'data': [ + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml' +] } \ No newline at end of file diff --git a/real_estate/data/ir.model.access.csv b/real_estate/data/ir.model.access.csv new file mode 100644 index 00000000000..682a453386d --- /dev/null +++ b/real_estate/data/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_user,access.estate.property,model_estate_property,base.group_user,1,1,1,1 diff --git a/real_estate/models/estate_property.py b/real_estate/models/estate_property.py index a30dd193f35..9cc4d30a4d9 100644 --- a/real_estate/models/estate_property.py +++ b/real_estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import fields, models +from datetime import date, timedelta class estate_property(models.Model): _name = "estate.property" @@ -7,7 +8,11 @@ class estate_property(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False) + date_availability = fields.Date( + string="Available From", + copy=False, + default=lambda self: date.today() + timedelta(days=90) + ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -21,4 +26,20 @@ class estate_property(models.Model): selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="Direction of the garden" - ) \ No newline at end of file + ) + active = fields.Boolean(default=True) + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], + required=True, + copy=False, + default='new' +) + + + \ No newline at end of file diff --git a/real_estate/security/ir.model.access.csv b/real_estate/security/ir.model.access.csv new file mode 100644 index 00000000000..baafdc83896 --- /dev/null +++ b/real_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_user,access.estate.property,model_estate_property,,1,1,1,1 diff --git a/real_estate/views/estate_menus.xml b/real_estate/views/estate_menus.xml new file mode 100644 index 00000000000..fbe5d4d7f79 --- /dev/null +++ b/real_estate/views/estate_menus.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/real_estate/views/estate_property_views.xml b/real_estate/views/estate_property_views.xml new file mode 100644 index 00000000000..3a777f16533 --- /dev/null +++ b/real_estate/views/estate_property_views.xml @@ -0,0 +1,100 @@ + + + + Properties + estate.property + list,form + + + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file From 1601aac245904e1091759e3a6cf2d73f5fd604b2 Mon Sep 17 00:00:00 2001 From: prbo-odoo Date: Mon, 7 Jul 2025 10:19:36 +0530 Subject: [PATCH 3/7] [ADD] real_estate: add offer status buttons and state transitions on properties MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces action buttons to accept or refuse offers in the real_estate module. When an offer is accepted, the property’s selling price, buyer, and state are updated accordingly. Other offers are automatically refused to ensure only one offer is accepted per property. Also added: - 'state' field on properties to manage lifecycle (new, offer_received, sold, cancelled) - Computed best price field from offers - Validity-based deadline logic using compute/inverse methods on offers - Safeguards preventing sale of cancelled properties and cancellation of sold ones --- real_estate/__manifest__.py | 3 + real_estate/models/__init__.py | 3 + real_estate/models/estate_property.py | 58 +++++++++++++++++-- real_estate/models/estate_property_offer.py | 51 ++++++++++++++++ real_estate/models/estate_property_tag.py | 7 +++ real_estate/models/estate_property_type.py | 7 +++ real_estate/security/ir.model.access.csv | 5 +- real_estate/views/estate_menus.xml | 12 ++++ .../views/estate_property_offer_view.xml | 39 +++++++++++++ .../views/estate_property_tag_view.xml | 30 ++++++++++ .../views/estate_property_type_view.xml | 8 +++ real_estate/views/estate_property_views.xml | 43 ++++++++++++-- 12 files changed, 257 insertions(+), 9 deletions(-) create mode 100644 real_estate/models/estate_property_offer.py create mode 100644 real_estate/models/estate_property_tag.py create mode 100644 real_estate/models/estate_property_type.py create mode 100644 real_estate/views/estate_property_offer_view.xml create mode 100644 real_estate/views/estate_property_tag_view.xml create mode 100644 real_estate/views/estate_property_type_view.xml diff --git a/real_estate/__manifest__.py b/real_estate/__manifest__.py index 18e57546169..b7e16426163 100644 --- a/real_estate/__manifest__.py +++ b/real_estate/__manifest__.py @@ -10,6 +10,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', + 'views/estate_property_type_view.xml', + 'views/estate_property_tag_view.xml', + 'views/estate_property_offer_view.xml', 'views/estate_menus.xml' ] } \ No newline at end of file diff --git a/real_estate/models/__init__.py b/real_estate/models/__init__.py index 5e1963c9d2f..09b2099fe84 100644 --- a/real_estate/models/__init__.py +++ b/real_estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer \ No newline at end of file diff --git a/real_estate/models/estate_property.py b/real_estate/models/estate_property.py index 9cc4d30a4d9..b6caa660cad 100644 --- a/real_estate/models/estate_property.py +++ b/real_estate/models/estate_property.py @@ -1,10 +1,12 @@ -from odoo import fields, models +from odoo import fields, models, api from datetime import date, timedelta +from odoo.exceptions import UserError class estate_property(models.Model): _name = "estate.property" _description = "Test Model" + name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() @@ -15,12 +17,17 @@ class estate_property(models.Model): ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) + + best_price = fields.Float(compute="_compute_best_price") + bedrooms = fields.Integer(default=2) - living_area = fields.Integer() + facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() - garden_area = fields.Integer() + living_area = fields.Float() + garden_area = fields.Float() + total_area = fields.Float(compute="_compute_total_area") garden_orientation = fields.Selection( string='Garden Orientation', selection=[('north', 'North'), ('south', 'South'), @@ -40,6 +47,49 @@ class estate_property(models.Model): copy=False, default='new' ) + property_type = fields.Many2one("estate.property.type", string="Property Type", default=None) + buyer = fields.Many2one("res.partner", string="Buyer", copy=False) + seller = fields.Many2one("res.partner", string="Salesperson", default=lambda self: self.env.user) + tag_ids = fields.Many2many("estate.property.tag", string="Tags", default=None) + offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") + + @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: + if record.offer_ids: + record.best_price = max(record.offer_ids.mapped("price")) + else: + record.best_price = 0.0 + + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = False + + + def action_sold(self): + for record in self: + if record.state == 'cancelled': + raise UserError("Cancelled property cannot be sold.") + record.state = 'sold' + return True + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError("Sold property cannot be cancelled.") + record.state = 'cancelled' + return True - \ No newline at end of file diff --git a/real_estate/models/estate_property_offer.py b/real_estate/models/estate_property_offer.py new file mode 100644 index 00000000000..82c41fdfde3 --- /dev/null +++ b/real_estate/models/estate_property_offer.py @@ -0,0 +1,51 @@ +from odoo import models, fields, api +from datetime import timedelta +from odoo.exceptions import UserError + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Property Offer" + + price = fields.Float() + status = fields.Selection([ + ('accepted', 'Accepted'), + ('refused', 'Refused') + ]) + partner_id = fields.Many2one("res.partner", string="Partner", required=True) + property_id = fields.Many2one("estate.property", string="Property", required=True) + + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline") + + @api.depends("create_date", "validity") + def _compute_date_deadline(self): + for record in self: + create_date = record.create_date or fields.Date.today() + record.date_deadline = create_date + timedelta(days=record.validity) + print("Computed date_deadline:", record.date_deadline) + + def _inverse_date_deadline(self): + for record in self: + create_date = record.create_date.date() if record.create_date else fields.Date.today() + record.validity = (record.date_deadline - create_date).days + + + def action_accept(self): + for record in self: + if record.property_id.state == 'sold': + raise UserError("You cannot accept an offer on a sold property.") + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer = record.partner_id + record.property_id.state = 'offer_accepted' + + other_offers = self.search([ + ('property_id', '=', record.property_id.id), + ('id', '!=', record.id) + ]) + other_offers.write({'status': 'refused'}) + + def action_refuse(self): + for offer in self: + offer.status = 'refused' + return True diff --git a/real_estate/models/estate_property_tag.py b/real_estate/models/estate_property_tag.py new file mode 100644 index 00000000000..902eaac884f --- /dev/null +++ b/real_estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class estate_property_tag(models.Model): + _name = "estate.property.tag" + _description = "Property Tag" + + name = fields.Char(required=True) diff --git a/real_estate/models/estate_property_type.py b/real_estate/models/estate_property_type.py new file mode 100644 index 00000000000..c549ee85e64 --- /dev/null +++ b/real_estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class estate_property_type(models.Model): + _name = "estate.property.type" + _description = "property type" + name = fields.Char(required=True) \ No newline at end of file diff --git a/real_estate/security/ir.model.access.csv b/real_estate/security/ir.model.access.csv index baafdc83896..6af596881bc 100644 --- a/real_estate/security/ir.model.access.csv +++ b/real_estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_user,access.estate.property,model_estate_property,,1,1,1,1 +access_estate_property_user,access.estate.property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,access.estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,access.estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,access.estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/real_estate/views/estate_menus.xml b/real_estate/views/estate_menus.xml index fbe5d4d7f79..6f51d831c63 100644 --- a/real_estate/views/estate_menus.xml +++ b/real_estate/views/estate_menus.xml @@ -12,4 +12,16 @@ action="action_estate_property" sequence="10" /> + + + + + + \ No newline at end of file diff --git a/real_estate/views/estate_property_offer_view.xml b/real_estate/views/estate_property_offer_view.xml new file mode 100644 index 00000000000..26ca83e510e --- /dev/null +++ b/real_estate/views/estate_property_offer_view.xml @@ -0,0 +1,39 @@ + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + + + estate.property.offer.form + estate.property.offer + +
+
+
+ + + + + + + + + + +
+
+
+
\ No newline at end of file diff --git a/real_estate/views/estate_property_tag_view.xml b/real_estate/views/estate_property_tag_view.xml new file mode 100644 index 00000000000..eb10b67146e --- /dev/null +++ b/real_estate/views/estate_property_tag_view.xml @@ -0,0 +1,30 @@ + + + + estate.property.tag.list + estate.property.tag + + + + + + + + + estate.property.tag.form + estate.property.tag + +
+ + +
+
+ + + Property Tags + estate.property.tag + list,form + + + +
diff --git a/real_estate/views/estate_property_type_view.xml b/real_estate/views/estate_property_type_view.xml new file mode 100644 index 00000000000..e8cc7f075d9 --- /dev/null +++ b/real_estate/views/estate_property_type_view.xml @@ -0,0 +1,8 @@ + + + + Property Types + estate.property.type + list,form + + \ No newline at end of file diff --git a/real_estate/views/estate_property_views.xml b/real_estate/views/estate_property_views.xml index 3a777f16533..f3ed881e3c5 100644 --- a/real_estate/views/estate_property_views.xml +++ b/real_estate/views/estate_property_views.xml @@ -19,6 +19,7 @@ + @@ -28,20 +29,28 @@ estate.property
+
+

+ - + + + + @@ -49,7 +58,6 @@ - @@ -60,7 +68,33 @@ - + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+ + - Property Types - estate.property.type - list,form - - \ No newline at end of file + Property Types + estate.property.type + list,form + + diff --git a/real_estate/views/estate_property_views.xml b/real_estate/views/estate_property_views.xml index f3ed881e3c5..710a82a550a 100644 --- a/real_estate/views/estate_property_views.xml +++ b/real_estate/views/estate_property_views.xml @@ -4,23 +4,31 @@ Properties estate.property list,form + {'search_default_available': 1} - + estate.property.list estate.property - + + - + + + @@ -29,20 +37,33 @@ estate.property
-
-
+ + +