From b97bbe11e2d4efe44d9a781fe5e5dbfe8e0f89de Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Wed, 3 Dec 2025 14:58:48 +0530 Subject: [PATCH 01/13] [ADD] estate: added init file and manifest file to make it a module Created basic module structure Chapter 2 --- estate/__init__.py | 0 estate/__manifest__.py | 12 ++++++++++++ 2 files changed, 12 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..b60e58b0b20 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,12 @@ +{ + 'name': "Real Estate", + 'version': '1.0', + 'depends': ['base'], + 'author': "Shivam Saksham(shsak)", + 'category': 'Sales', + 'description': """ + An Real Estate App to buy, sell, and rent properties. + """, + 'application': True, + 'license': 'LGPL-3' +} From 3c98f6120be8f83495bbbdba4eaa755b489d4b80 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Thu, 4 Dec 2025 11:32:30 +0530 Subject: [PATCH 02/13] [IMP] estate: Create estate property model with more field set Adds the main estate property model and includes a broad set of fields to better describe each property. Chapter 3 --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2d..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/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..78dda0d6a6a --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,23 @@ +from odoo import models, fields + + +class EstateProperty(models.Model): + _name = "estate_property" + _description = "Estate Property details" + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + expected_price = fields.Float(required=True) + selling_price = fields.Float() + bedrooms = fields.Integer() + 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')] + ) From 9a9053897f50fe731a5ffc10b349c2b041d9ba1d Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Thu, 4 Dec 2025 12:30:24 +0530 Subject: [PATCH 03/13] [IMP] estate: Grant base users full access to estate property by security rules Provided the base user group with all the permission for the Estate.property model. Chapter 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 b60e58b0b20..4c814f3f75b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -8,5 +8,8 @@ An Real Estate App to buy, sell, and rent properties. """, 'application': True, - 'license': 'LGPL-3' + 'license': 'LGPL-3', + 'data': [ + 'security/ir.model.access.csv', + ], } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..fe21e56c6d2 --- /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 From 5a8d5d3478fa570fab518e64a521b7776f01d907 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Thu, 4 Dec 2025 17:10:44 +0530 Subject: [PATCH 04/13] [IMP] estate: Added menus, actions, and views for the UI Added menus to show the application on the odoo root menu Action to open the view List and form view to Create, Edit, and Show Estate Property Chapter 5 --- estate/__manifest__.py | 2 ++ estate/models/estate_property.py | 16 ++++++++++++---- estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 7 +++++++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4c814f3f75b..70ade2f66c8 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,5 +11,7 @@ 'license': 'LGPL-3', 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 78dda0d6a6a..38ef759a90c 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,16 +8,24 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date(default=fields.Date.add(fields.Date.today(), month=3), copy=False) expected_price = fields.Float(required=True) - selling_price = fields.Float() - bedrooms = fields.Integer() + 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', + string='Garden Orientation', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')] ) + state = fields.Selection( + string='Status', + selection=[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')], + default='new', + required=True, + copy=False + ) + active = fields.Boolean(default=True) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..17a1223dc5d --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..ef6aab5f4be --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate_property + list,form + + \ No newline at end of file From b31e223405b35d47a1e4e73fc386462d000fc849 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Fri, 5 Dec 2025 15:54:02 +0530 Subject: [PATCH 05/13] [IMP] estate: Added form, search, filters, groups views Allows users to search across multiple fields Enables filtered results and grouping by various parameters Chapter 6 --- estate/models/estate_property.py | 5 +- estate/views/estate_property_views.xml | 73 ++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 38ef759a90c..58bac3894dc 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -8,7 +8,10 @@ class EstateProperty(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(default=fields.Date.add(fields.Date.today(), month=3), copy=False) + date_availability = fields.Date( + default=lambda self: fields.Date.add(fields.Date.today(), months=3), + copy=False + ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index ef6aab5f4be..32b10970184 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,4 +1,77 @@ + + estate_property_form + estate_property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + Properties List + estate_property + + + + + + + + + + + + + + + Properties Search + estate_property + + + + + + + + + + + + + + Properties estate_property From 502ebb0cbe05b458f7a20de5c52a5878977cdd04 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Mon, 8 Dec 2025 14:03:39 +0530 Subject: [PATCH 06/13] [IMP] estate: Implement relations between modules Added relational fields Chapter 7 --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 3 ++ estate/models/estate_property.py | 5 ++++ estate/models/estate_property_offer.py | 15 ++++++++++ estate/models/estate_property_tag.py | 8 ++++++ estate/models/estate_property_type.py | 8 ++++++ estate/security/ir.model.access.csv | 5 +++- estate/views/estate_menus.xml | 8 +++++- estate/views/estate_property_offer_views.xml | 29 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 7 +++++ estate/views/estate_property_type_views.xml | 7 +++++ estate/views/estate_property_views.xml | 17 +++++++++++- 12 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 estate/models/estate_property_offer.py create mode 100644 estate/models/estate_property_tag.py create mode 100644 estate/models/estate_property_type.py create mode 100644 estate/views/estate_property_offer_views.xml create mode 100644 estate/views/estate_property_tag_views.xml create mode 100644 estate/views/estate_property_type_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 70ade2f66c8..57a9297376d 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,6 +12,9 @@ '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/estate_menus.xml', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 58bac3894dc..c1b81a58c88 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -32,3 +32,8 @@ class EstateProperty(models.Model): copy=False ) active = fields.Boolean(default=True) + property_type_id = fields.Many2one("estate.property.type", string="Type") + buyer = fields.Many2one("res.partner", copy=False) + salesperson = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) + tag_ids = fields.Many2many("estate.property.tag", string="Tag") + 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..e4dd65377b3 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,15 @@ +from odoo import models, fields + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Offer to buy the property" + + price = fields.Float() + status = fields.Selection( + string="Status", + selection=[('accepted', 'Accepted'), ('refused', 'Refused')], + copy=False, + ) + partner_id = fields.Many2one("res.partner", required=True) + property_id = fields.Many2one("estate_property", required=True) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..8ea1d99ff20 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Tag for the property" + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..b4658368496 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import models, fields + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Defines the type of Real Estate Property" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index fe21e56c6d2..c79331f2f1c 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +estate.access_estate_property,access_estate_property,estate.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 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 17a1223dc5d..0e4b3311596 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,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 new file mode 100644 index 00000000000..9f78ebf6c06 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,29 @@ + + + estate_property_offer_form + estate.property.offer + + + + + + + + + + + Property Offer List + estate.property.offer + +
+ + + + + + + +
+
+
+
\ No newline at end of file diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 00000000000..3920037c137 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,7 @@ + + + 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..6cbd93a23cf --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,7 @@ + + + Property Types + estate.property.type + list,form + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 32b10970184..1efe25f1697 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -8,8 +8,10 @@

+ + @@ -20,7 +22,7 @@ - + @@ -32,6 +34,17 @@ + + + + + + + + + + + @@ -45,6 +58,7 @@ + @@ -65,6 +79,7 @@ + From 859c071c0fcd436526e6640ed8e3454fe0b5132b Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Mon, 8 Dec 2025 18:07:46 +0530 Subject: [PATCH 07/13] [IMP] estate: Add compute fields and onchanges for offer and garden Added compute logic for offer validity and deadline_date Implemented onchange methods to set default garden area values Chapter 8 --- estate/__manifest__.py | 4 +-- estate/models/estate_property.py | 35 ++++++++++++++++---- estate/models/estate_property_offer.py | 26 +++++++++++---- estate/security/ir.model.access.csv | 2 +- estate/views/estate_menus.xml | 3 +- estate/views/estate_property_offer_views.xml | 6 +++- estate/views/estate_property_tag_views.xml | 2 +- estate/views/estate_property_type_views.xml | 2 +- estate/views/estate_property_views.xml | 4 ++- 9 files changed, 63 insertions(+), 21 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 57a9297376d..ccda78c6407 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,8 +1,8 @@ { - 'name': "Real Estate", + 'name': 'Real Estate', 'version': '1.0', 'depends': ['base'], - 'author': "Shivam Saksham(shsak)", + 'author': 'Shivam Saksham(shsak)', 'category': 'Sales', 'description': """ An Real Estate App to buy, sell, and rent properties. diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c1b81a58c88..2f4953e7591 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,9 +1,9 @@ -from odoo import models, fields +from odoo import models, fields, api class EstateProperty(models.Model): - _name = "estate_property" - _description = "Estate Property details" + _name = 'estate_property' + _description = 'Estate Property details' name = fields.Char(required=True) description = fields.Text() @@ -32,8 +32,29 @@ class EstateProperty(models.Model): copy=False ) active = fields.Boolean(default=True) - property_type_id = fields.Many2one("estate.property.type", string="Type") - buyer = fields.Many2one("res.partner", copy=False) - salesperson = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.user) - tag_ids = fields.Many2many("estate.property.tag", string="Tag") + property_type_id = fields.Many2one('estate.property.type', string='Type') + buyer = fields.Many2one('res.partner', copy=False) + salesperson = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) + tag_ids = fields.Many2many('estate.property.tag', string='Tag') offer_ids = fields.One2many('estate.property.offer', 'property_id') + total_area = fields.Integer(compute='_compute_total_area') + best_price = fields.Float(compute='_compute_best_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 + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price') or [0]) + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_orientation = 'north' + self.garden_area = 10 + else: + self.garden_orientation = None + self.garden_area = 0 diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index e4dd65377b3..6a509ec0e18 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,15 +1,29 @@ -from odoo import models, fields +from odoo import models, fields, api class EstatePropertyOffer(models.Model): - _name = "estate.property.offer" - _description = "Offer to buy the property" + _name = 'estate.property.offer' + _description = 'Offer to buy the property' price = fields.Float() status = fields.Selection( - string="Status", + string='Status', selection=[('accepted', 'Accepted'), ('refused', 'Refused')], copy=False, ) - partner_id = fields.Many2one("res.partner", required=True) - property_id = fields.Many2one("estate_property", required=True) + partner_id = fields.Many2one('res.partner', required=True) + property_id = fields.Many2one('estate_property', required=True) + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute='_compute_offer_date_deadline', inverse='_inverse_offer_date_deadline') + + @api.depends('create_date', 'validity') + def _compute_offer_date_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = fields.Date.add(record.create_date.date(), days=record.validity) + else: + record.date_deadline = fields.Date.add(fields.Date.today(), days=record.validity) + + def _inverse_offer_date_deadline(self): + for record in self: + record.validity = (record.date_deadline - record.create_date.date()).days diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index c79331f2f1c..78458c1cb61 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 estate.access_estate_property,access_estate_property,estate.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 \ No newline at end of file +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/views/estate_menus.xml b/estate/views/estate_menus.xml index 0e4b3311596..f0841d8e7d1 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -3,6 +3,7 @@ + @@ -10,4 +11,4 @@ action="estate_property_tag_action" /> -
\ 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 9f78ebf6c06..b89f4ee3736 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -6,6 +6,8 @@ + + @@ -20,10 +22,12 @@ + + - \ 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 3920037c137..bbb5013739c 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -4,4 +4,4 @@ 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 index 6cbd93a23cf..382925bd424 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -4,4 +4,4 @@ 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 1efe25f1697..3cd00fcaff0 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -18,6 +18,7 @@ + @@ -32,6 +33,7 @@ + @@ -92,4 +94,4 @@ estate_property list,form - \ No newline at end of file + From d46ed71b1644b03d5d939503e2e24c35d7dc8f8f Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Tue, 9 Dec 2025 18:13:57 +0530 Subject: [PATCH 08/13] [IMP] estate: Added Action button in Property and Offer Model In estate.property model added the action button 1. to cancel the property advertisement 2. to mark the property sold In estate.offer model added the action button 1. to accept the offer for the particular property 2. to refuse the offer for the particular property 3. set buyer and selling price, when any offer is accepted Chapter 9 --- estate/models/estate_property.py | 18 +++++++++++++++++- estate/models/estate_property_offer.py | 18 +++++++++++++++++- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 12 +++++++++--- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2f4953e7591..0a36a4d4054 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, exceptions class EstateProperty(models.Model): @@ -58,3 +58,19 @@ def _onchange_garden(self): else: self.garden_orientation = None self.garden_area = 0 + + def cancel_property_sale(self): + for record in self: + if record.state == 'sold': + raise exceptions.UserError('A sold property cannot be cancelled') + else: + record.state = 'cancelled' + return True + + def set_property_sold(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError('A cancelled property cannot be Sold') + else: + record.state = 'sold' + return True diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 6a509ec0e18..07997d52b88 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, exceptions class EstatePropertyOffer(models.Model): @@ -27,3 +27,19 @@ def _compute_offer_date_deadline(self): def _inverse_offer_date_deadline(self): for record in self: record.validity = (record.date_deadline - record.create_date.date()).days + + def action_offer_accepted(self): + for record in self: + if record.status == 'accepted': + return False + if record.property_id.buyer: + raise exceptions.UserError('An another offer is already accepted') + record.property_id.selling_price = record.price + record.property_id.buyer = record.partner_id + record.status = 'accepted' + return True + + def action_offer_refused(self): + for record in self: + record.status = 'refused' + return True diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index b89f4ee3736..ee9d8ec3973 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -8,6 +8,8 @@ + + +

+ +

+ + + + + + + + + + + + + +
+ + + + estate_property_type_list + estate.property.type + + + + + + + + Property Types estate.property.type diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 29a1947815e..f2645ac0058 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,17 +5,22 @@
-

- + - + @@ -34,15 +39,16 @@ - - - + + - + @@ -61,15 +67,17 @@ Properties List estate_property - + + - + @@ -83,7 +91,8 @@ - + Properties estate_property + {'search_default_available': True} list,form From 7d95cad9472c5ddb2d7e9a0c3d48a20d15984737 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Tue, 16 Dec 2025 14:31:14 +0530 Subject: [PATCH 11/13] [IMP] estate: Modify the res.user to add property_ids by inheritance Overrides ondelete and create method for the models - on delete for property model - on create, offer to check if an offer with a higher bid already exists or not Inherited the res. user to add a new property_id field to it so that we can list all the properties associated with the User's account. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 5 +++++ estate/models/estate_property_offer.py | 11 +++++++++++ estate/models/res_users.py | 11 +++++++++++ estate/views/res_users_views.xml | 14 ++++++++++++++ 6 files changed, 43 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 ccda78c6407..b6a9603b680 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -15,6 +15,7 @@ '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', ], } diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8697384c6c7..31a1bae4129 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -103,3 +103,8 @@ def action_property_sold(self): self.state = 'sold' return True + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_sold(self): + if self.filtered(lambda x: x.state not in ('new', 'cancelled')): + raise exceptions.UserError(_('Cannot delete property which is not yer sold or cancelled')) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 8199ea573ce..c6d11e2e030 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -49,3 +49,14 @@ def action_offer_refused(self): for record in self: record.status = 'refused' return True + + @api.model + def create(self, vals_list): + if len(vals_list) > 0: + prop = self.env['estate_property'].browse(vals_list[0]['property_id']) + if prop.best_price > vals_list[0]['price']: + raise exceptions.UserError(_('An offer with high price already exists.')) + + prop.state = 'offer_received' + + return super().create(vals_list) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..31e362e2cd5 --- /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( + 'estate_property', + 'salesperson', + domain=['|', ('state', '=', 'new'), ('state', '=', 'offer_received')] + ) diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 00000000000..e3b24840a4e --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,14 @@ + + + res.users.view.form.inherit.estate + res.users + + + + + + + + + + From b32016b43c9cd7327102b21e104e38bd7f067d43 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Wed, 17 Dec 2025 12:09:57 +0530 Subject: [PATCH 12/13] [IMP] estate: Create New module estate_account to create Property invoice Will add a new module that depends on the estate and account module It overrides the sold action to create an invoice for the property sold --- estate/models/estate_property.py | 104 ++++++++++++----------- estate/views/estate_property_views.xml | 12 ++- estate_account/__init__.py | 1 + estate_account/__manifest__.py | 12 +++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 30 +++++++ 6 files changed, 108 insertions(+), 52 deletions(-) create mode 100644 estate_account/__init__.py create mode 100644 estate_account/__manifest__.py create mode 100644 estate_account/models/__init__.py create mode 100644 estate_account/models/estate_property.py diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 31a1bae4129..295b5089051 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -3,16 +3,15 @@ class EstateProperty(models.Model): - _name = 'estate_property' - _description = 'Estate Property details' - _order = 'id desc' + _name = "estate_property" + _description = "Estate Property details" + _order = "id desc" name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() date_availability = fields.Date( - default=lambda self: fields.Date.add(fields.Date.today(), months=3), - copy=False + default=lambda self: fields.Date.add(fields.Date.today(), months=3), copy=False ) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) @@ -23,88 +22,97 @@ class EstateProperty(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Garden Orientation', + string="Garden Orientation", selection=[ - ('north', 'North'), - ('south', 'South'), - ('east', 'East'), - ('west', 'West') - ] + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), + ], ) state = fields.Selection( - string='Status', + string="Status", 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"), ], - default='new', + default="new", required=True, - copy=False + copy=False, ) active = fields.Boolean(default=True) - property_type_id = fields.Many2one('estate.property.type', string='Type') - buyer = fields.Many2one('res.partner', copy=False) - salesperson = fields.Many2one('res.users', string='Salesman', default=lambda self: self.env.user) - tag_ids = fields.Many2many('estate.property.tag', string='Tag') - offer_ids = fields.One2many('estate.property.offer', 'property_id') - total_area = fields.Integer(compute='_compute_total_area') - best_price = fields.Float(compute='_compute_best_price') + property_type_id = fields.Many2one("estate.property.type", string="Type") + buyer = fields.Many2one("res.partner", copy=False) + salesperson = fields.Many2one( + "res.users", string="Salesman", default=lambda self: self.env.user + ) + tag_ids = fields.Many2many("estate.property.tag", string="Tag") + offer_ids = fields.One2many("estate.property.offer", "property_id") + total_area = fields.Integer(compute="_compute_total_area") + best_price = fields.Float(compute="_compute_best_price") _check_expected_price = models.Constraint( - 'CHECK(expected_price > 0)', - 'The Expected price cannot be 0 or less then 0' + "CHECK(expected_price > 0)", "The Expected price cannot be 0 or less then 0" ) _check_selling_price = models.Constraint( - 'CHECK(selling_price >= 0)', - 'The Selling price cannot be less then 0' + "CHECK(selling_price >= 0)", "The Selling price cannot be less then 0" ) - @api.constrains('selling_price', 'buyer', 'expected_price') + @api.constrains("selling_price", "buyer", "expected_price") def _check_selling_price_90p(self): for record in self: if record.selling_price == 0: return False - if float_compare((record.selling_price / record.expected_price) * 100, 90, precision_digits=2) < 0: - raise exceptions.ValidationError(_('Selling Price should be 90% or more of expected price.')) + if ( + float_compare( + (record.selling_price / record.expected_price) * 100, + 90, + precision_digits=2, + ) + < 0 + ): + raise exceptions.ValidationError( + _("Selling Price should be 90% or more of expected price.") + ) - @api.depends('living_area', 'garden_area') + @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') + @api.depends("offer_ids.price") def _compute_best_price(self): for record in self: - record.best_price = max(record.offer_ids.mapped('price') or [0]) + record.best_price = max(record.offer_ids.mapped("price") or [0]) - @api.onchange('garden') + @api.onchange("garden") def _onchange_garden(self): if self.garden: - self.garden_orientation = 'north' + self.garden_orientation = "north" self.garden_area = 10 else: self.garden_orientation = None self.garden_area = 0 def action_cancel_property(self): - if self.filtered(lambda x: x.state == 'sold'): - raise exceptions.UserError(_('A sold property cannot be cancelled')) + if self.filtered(lambda x: x.state == "sold"): + raise exceptions.UserError(_("A sold property cannot be cancelled")) - self.state = 'cancelled' - return True + self.state = "cancelled" def action_property_sold(self): - if self.filtered(lambda x: x.state == 'cancelled'): - raise exceptions.UserError(_('A cancelled property cannot be sold')) + if self.filtered(lambda x: x.state == "cancelled"): + raise exceptions.UserError(_("A cancelled property cannot be sold")) - self.state = 'sold' - return True + self.state = "sold" @api.ondelete(at_uninstall=False) def _unlink_if_new_or_sold(self): - if self.filtered(lambda x: x.state not in ('new', 'cancelled')): - raise exceptions.UserError(_('Cannot delete property which is not yer sold or cancelled')) + if self.filtered(lambda x: x.state not in ("new", "cancelled")): + raise exceptions.UserError( + _("Cannot delete property which is new or cancelled") + ) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f2645ac0058..17c4863af60 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -15,8 +15,12 @@

- + + + + + Properties estate_property {'search_default_available': True} - list,form + list,form,kanban - + \ No newline at end of file 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..7448d7ce0ce --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,12 @@ +{ + "name": "Estate Accounting", + "version": "1.0", + "depends": ["estate", "account"], + "author": "Shivam Saksham(shsak)", + "category": "Sales", + "description": """ + A Real Estate Invoicing Module + """, + "license": "LGPL-3", + "data": [], +} diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 00000000000..fe5d23303f2 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,30 @@ +from odoo import models, Command + + +class EstateProperty(models.Model): + _inherit = "estate_property" + + def action_property_sold(self): + self.env["account.move"].create( + { + "move_type": "out_invoice", + "partner_id": self.buyer.id, + "invoice_line_ids": [ + Command.create( + { + "name": self.name, + "quantity": 1, + "price_unit": self.selling_price * 0.6, + } + ), + Command.create( + { + "name": "Administrative Fees", + "quantity": 1, + "price_unit": 100, + } + ), + ], + } + ) + return super().action_property_sold() From 6ecbb26e1dc3312084724c9f2f9e824ce92b4113 Mon Sep 17 00:00:00 2001 From: shsak-odoo Date: Thu, 18 Dec 2025 15:41:44 +0530 Subject: [PATCH 13/13] [IMP] estate: add the kanban view for the property list Properties can now be grouped by type in the Kanban view Chapter: 14 --- estate/views/estate_property_tag_views.xml | 1 + estate/views/estate_property_views.xml | 36 +++++++++++++++++++--- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml index 522441a4311..778a5d90cc2 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -5,6 +5,7 @@ + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 17c4863af60..d5103e50026 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,5 +1,5 @@ - + estate_property_form estate_property @@ -67,7 +67,7 @@ - + Properties List estate_property @@ -75,7 +75,7 @@ decoration-bf="state == 'offer_accepted'" decoration-muted="state == 'sold'"> - + @@ -86,7 +86,7 @@ - + Properties Search estate_property @@ -106,10 +106,36 @@ + + Estate Property Kanban + estate_property + + + + + +
+ +
Expected Price: +
+
Best Price: +
+ + + + +
+
+
+
+
+
+ Properties estate_property {'search_default_available': True} list,form,kanban -
\ No newline at end of file +