From 6b97ae5aa33dc1d319032aafb2082a7cd64388a4 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Mon, 11 Aug 2025 17:57:49 +0530 Subject: [PATCH 01/14] [ADD] estate: created estate advertisement module . Introduce a new module for real estate advertisements . Ensure it appears in the Apps list for installation . Provide basic description and dependencies for recognition . Allow it to be installed as an application (empty shell for now) --- estate/__init__.py | 0 estate/__manifest__.py | 5 +++++ 2 files changed, 5 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..97c1160f50c --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,5 @@ +{ + 'name': 'Real Estate', + 'application': True, + 'installable': True +} \ No newline at end of file From 42f72a9f4e087af47ba3578d28427b096715ee4b Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Tue, 12 Aug 2025 10:02:25 +0530 Subject: [PATCH 02/14] [ADD] estate: defined estate property model . Create a model for properties with basic info, availability, and pricing. . Include structural details and amenities like garden size and orientation. . Require key fields such as name and expected price. . Ensure data is saved properly via the ORM. --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 22 ++++++++++++++++++++++ 3 files changed, 24 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..9a7e03eded3 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/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..c34b2814e39 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,22 @@ +from odoo import models, fields + +class EstateModel(models.Model): + _name = "estate.property" + _description = "Real Estate Advertisement Model" + + 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='Type', + selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West')], + ) \ No newline at end of file From ae1096610cf38c5d54651a188191277f0d271dec Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Tue, 12 Aug 2025 16:30:03 +0530 Subject: [PATCH 03/14] [ADD] estate: added security access configuration . Allow users to read, create, update, and delete property records. . Eliminate warnings related to missing access rules for the property model. . Implement default security settings to grant standard users proper data access. --- estate/__manifest__.py | 6 +++++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 97c1160f50c..d2644952990 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,5 +1,9 @@ { 'name': 'Real Estate', 'application': True, - 'installable': True + 'installable': True, + 'category': 'Real Estate/Brokerage', + 'data': [ + 'security/ir.model.access.csv' + ] } \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..fd62fe01321 --- /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_user,model_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From 25213feb8a768b2d4eabcbb1236c05002da62329 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Mon, 18 Aug 2025 19:10:54 +0530 Subject: [PATCH 04/14] [ADD] estate: added menus options and new fields . Add root, first-level, and action menus for estate properties. . Create list and form views for property records. . Make selling price read-only and exclude it and availability date from duplicates. . Set default bedrooms to 2 and availability date to 3 months ahead. . Add active field (default True) and state field with key property statuses. --- estate/__manifest__.py | 4 +++- estate/models/estate_property.py | 25 +++++++++++++++---- estate/views/estate_menus.xml | 9 +++++++ estate/views/estate_property_views.xml | 33 ++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 6 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 d2644952990..e9f14da3333 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,6 +4,8 @@ 'installable': True, 'category': 'Real Estate/Brokerage', 'data': [ - 'security/ir.model.access.csv' + '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/estate_property.py b/estate/models/estate_property.py index c34b2814e39..89dd45c45c0 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,5 @@ from odoo import models, fields +from datetime import timedelta class EstateModel(models.Model): _name = "estate.property" @@ -7,16 +8,30 @@ class EstateModel(models.Model): name = fields.Char(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() + date_availability = fields.Date( + copy=False, + default= fields.Date.today() + timedelta(days=90) + ) + active= fields.Boolean(default=True) 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() + state= fields.Selection( + string='state', + default="New", + required=True, + copy=False, + selection=[('New', 'New'),('Offer Received', 'Offer Received'),('Offer Accepted', 'Offer Accepted'),('Sold', 'Sold'), ('Cancelled', 'Cancelled'), + ] + ) garden_orientation = fields.Selection( - string='Type', - selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West')], + string='Garden Orientation', + selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West'), + ], + help="Select the direction the garden faces" ) \ 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..952789b6d35 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..22c5141320c --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,33 @@ + + + + + Properties + estate.property + list,form + +

+ Create the first property +

+
+
+ + +
\ No newline at end of file From 8afd3882d705b43261aeff75164100e32ee71dea Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Tue, 19 Aug 2025 14:35:38 +0530 Subject: [PATCH 05/14] [ADD] estate: customized form and list view and added filtering options . Add a list view displaying key property fields like name, price, and bedrooms. . Organize the form view using groups and notebook tabs for clarity. . Implement a search view with filters on name and availability. . Add a filter to display only available properties (New, Offer Received). . Provide a shortcut to group the property list by postcode. --- estate/views/estate_property_views.xml | 72 +++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 22c5141320c..299f8e34337 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -12,22 +12,82 @@ - + + Estate Properties list estate.property + + + + + + + - - estate.property.form + + + Estate Property Form estate.property
- + +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + +
-
--> +
+ + + + Estate Property Search + estate.property + + + + + + + + + + + + + + + + + \ No newline at end of file From dec6f49dfd3afc071ed46f06d702a5f772c98afc Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Tue, 19 Aug 2025 19:06:29 +0530 Subject: [PATCH 06/14] [ADD] estate: added relational models and property types, tags, buyers and offers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit . Add property types with menus, actions, and views; link properties to types. . Include buyer and salesperson info in a new tab; default salesperson to current user, don’t copy buyer. . Implement property tags with menus, actions, and views; support multiple tags per property. . Introduce property offers with price, status, and partner details. . Link offers to properties and display them within the property form. --- estate/__manifest__.py | 5 +++- estate/models/__init__.py | 5 +++- estate/models/estate_property.py | 7 ++++- estate/models/estate_property_offer.py | 13 +++++++++ estate/models/estate_property_tag.py | 7 +++++ estate/models/estate_property_type.py | 7 +++++ estate/security/ir.model.access.csv | 6 ++++- estate/views/estate_menus.xml | 14 +++++++--- estate/views/estate_property_offer_views.xml | 28 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 15 +++++++++++ estate/views/estate_property_type_views.xml | 15 +++++++++++ estate/views/estate_property_views.xml | 20 ++++++++++++-- 12 files changed, 133 insertions(+), 9 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 e9f14da3333..6e6f82223e8 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -6,6 +6,9 @@ 'data': [ 'security/ir.model.access.csv', 'views/estate_property_views.xml', - 'views/estate_menus.xml' + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', + 'views/estate_menus.xml', ] } \ No newline at end of file diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6d..09b2099fe84 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ -from . import estate_property \ No newline at end of file +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/estate/models/estate_property.py b/estate/models/estate_property.py index 89dd45c45c0..49160338415 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -34,4 +34,9 @@ class EstateModel(models.Model): selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West'), ], help="Select the direction the garden faces" - ) \ No newline at end of file + ) + property_type_id= fields.Many2one("estate.property.type") + buyer= fields.Many2one("res.partner") + salesman= fields.Many2one("res.users") + tag_ids= fields.Many2many("estate.property.tag") + offer_ids= fields.One2many("estate.property.offer", inverse_name="property_id") \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..870c435a3cd --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,13 @@ +from odoo import models, fields + +class EstateOfferModel(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer Model" + + price = fields.Float() + status = fields.Selection([ + ('Accepted', 'Accepted'), + ('Refused', 'Refused') + ], copy=False) + partner_id= fields.Many2one('res.partner', required=True) + property_id= fields.Many2one('estate.property', required=True) \ No newline at end of file diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..6b6b7eaf5c6 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstateTagModel(models.Model): + _name = "estate.property.tag" + _description = "Real Estate Property Tag Model" + + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..50af47a3615 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstateTypeModel(models.Model): + _name = "estate.property.type" + _description = "Real Estate Property Type Model" + + name = fields.Char(required=True) \ No newline at end of file diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index fd62fe01321..47dae2d3b06 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,6 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property_user,model_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_user,model_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,model_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,model_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,model_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 + diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 952789b6d35..5a7f66b8478 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,9 +1,17 @@ - - - + + + + + + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..c3399946af1 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,28 @@ + + + + + Property Offers + estate.property.offer + list,form + +

+ Create the first property Type +

+
+
+ + + + Estate Properties 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..f3e32dd5030 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,15 @@ + + + + + Property Tag + estate.property.tag + list,form + +

+ Create the first property Tag +

+
+
+ +
\ 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..c4f069e082f --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,15 @@ + + + + + Property Types + estate.property.type + list,form + +

+ Create the first property Type +

+
+
+ +
\ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 299f8e34337..6eccb81ad0a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -36,12 +36,18 @@
-

+
+

+ + + +
+ @@ -63,6 +69,16 @@ + + + + + + + + + + @@ -82,7 +98,7 @@ - + From 830bfc6efc4e860e1084df1cea9a783a2c6a1ce9 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Wed, 20 Aug 2025 18:22:50 +0530 Subject: [PATCH 07/14] [ADD] estate: added auto computed fields to property and offer views . Property page displays total area by summing living space and garden. . Best offer price shown on property page for easy comparison. . Offer page includes a deadline calculated from validity days. . Editing deadline automatically updates validity days. . Garden details auto-fill when enabled and clear when removed; UI reflects these behaviors. --- estate/__init__.py | 2 +- estate/__manifest__.py | 26 ++++---- estate/models/__init__.py | 2 +- estate/models/estate_property.py | 65 +++++++++++++++----- estate/models/estate_property_offer.py | 32 +++++++--- estate/models/estate_property_tag.py | 3 +- estate/models/estate_property_type.py | 3 +- estate/views/estate_menus.xml | 14 +++-- estate/views/estate_property_offer_views.xml | 2 + estate/views/estate_property_views.xml | 7 ++- 10 files changed, 108 insertions(+), 48 deletions(-) diff --git a/estate/__init__.py b/estate/__init__.py index 9a7e03eded3..0650744f6bc 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -1 +1 @@ -from . import models \ No newline at end of file +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 6e6f82223e8..717f226463a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,14 +1,14 @@ { - 'name': 'Real Estate', - 'application': True, - 'installable': True, - 'category': 'Real Estate/Brokerage', - 'data': [ - 'security/ir.model.access.csv', - 'views/estate_property_views.xml', - 'views/estate_property_type_views.xml', - 'views/estate_property_tag_views.xml', - 'views/estate_property_offer_views.xml', - 'views/estate_menus.xml', - ] -} \ No newline at end of file + "name": "Real Estate", + "application": True, + "installable": True, + "category": "Real Estate/Brokerage", + "data": [ + "security/ir.model.access.csv", + "views/estate_property_views.xml", + "views/estate_property_type_views.xml", + "views/estate_property_tag_views.xml", + "views/estate_property_offer_views.xml", + "views/estate_menus.xml", + ], +} diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 09b2099fe84..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +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 +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 49160338415..70fe2d1cf9e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ -from odoo import models, fields +from odoo import models, fields, api from datetime import timedelta + class EstateModel(models.Model): _name = "estate.property" _description = "Real Estate Advertisement Model" @@ -9,10 +10,9 @@ class EstateModel(models.Model): description = fields.Text() postcode = fields.Char() date_availability = fields.Date( - copy=False, - default= fields.Date.today() + timedelta(days=90) - ) - active= fields.Boolean(default=True) + copy=False, default=fields.Date.today() + timedelta(days=90) + ) + active = fields.Boolean(default=True) expected_price = fields.Float(required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -21,22 +21,53 @@ class EstateModel(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() - state= fields.Selection( - string='state', + state = fields.Selection( + string="state", default="New", required=True, copy=False, - selection=[('New', 'New'),('Offer Received', 'Offer Received'),('Offer Accepted', 'Offer Accepted'),('Sold', 'Sold'), ('Cancelled', 'Cancelled'), - ] + selection=[ + ("New", "New"), + ("Offer Received", "Offer Received"), + ("Offer Accepted", "Offer Accepted"), + ("Sold", "Sold"), + ("Cancelled", "Cancelled"), + ], ) garden_orientation = fields.Selection( - string='Garden Orientation', - selection=[('north', 'North'),('south', 'South'),('east', 'East'),('west', 'West'), + string="Garden Orientation", + selection=[ + ("north", "North"), + ("south", "South"), + ("east", "East"), + ("west", "West"), ], - help="Select the direction the garden faces" + help="Select the direction the garden faces", ) - property_type_id= fields.Many2one("estate.property.type") - buyer= fields.Many2one("res.partner") - salesman= fields.Many2one("res.users") - tag_ids= fields.Many2many("estate.property.tag") - offer_ids= fields.One2many("estate.property.offer", inverse_name="property_id") \ No newline at end of file + property_type_id = fields.Many2one("estate.property.type") + buyer = fields.Many2one("res.partner") + salesman = fields.Many2one("res.users") + tag_ids = fields.Many2many("estate.property.tag") + offer_ids = fields.One2many("estate.property.offer", inverse_name="property_id") + total_area = fields.Float(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"), default=0.0) + + # the onchange method will access self which will hold the current form value object not all the value objects + @api.onchange("garden") + def _set_garden_default_values(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = "north" + else: + self.garden_area = 0 + self.garden_orientation = "" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 870c435a3cd..9c2dd881c91 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,13 +1,31 @@ -from odoo import models, fields +from odoo import models, fields, api +from datetime import timedelta + class EstateOfferModel(models.Model): _name = "estate.property.offer" _description = "Real Estate Property Offer Model" price = fields.Float() - status = fields.Selection([ - ('Accepted', 'Accepted'), - ('Refused', 'Refused') - ], copy=False) - partner_id= fields.Many2one('res.partner', required=True) - property_id= fields.Many2one('estate.property', required=True) \ No newline at end of file + status = fields.Selection( + [("Accepted", "Accepted"), ("Refused", "Refused")], copy=False + ) + 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(default=fields.Date.today() + timedelta(days=7)) + + @api.depends("validity", "create_date") + def _compute_date_deadline(self): + for record in self: + record.date_deadline = fields.Date.add( + (record.create_date or fields.date.today()), days=record.validity + ) + + def _inverse_date_deadline(self): + for record in self: + record.validity = ( + (record.date_deadline - record.create_date.date()).days + if record.date_deadline + else 0 + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 6b6b7eaf5c6..293a0f7841b 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,7 +1,8 @@ from odoo import models, fields + class EstateTagModel(models.Model): _name = "estate.property.tag" _description = "Real Estate Property Tag Model" - name = fields.Char(required=True) \ No newline at end of file + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 50af47a3615..d04b5ec5f0e 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,7 +1,8 @@ from odoo import models, fields + class EstateTypeModel(models.Model): _name = "estate.property.type" _description = "Real Estate Property Type Model" - name = fields.Char(required=True) \ No newline at end of file + name = fields.Char(required=True) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 5a7f66b8478..c37ed208a70 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -4,14 +4,18 @@ - + - - - + + + - + \ 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 c3399946af1..9c9a1c59128 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -21,6 +21,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 6eccb81ad0a..6d34295abd4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -52,6 +52,7 @@ + @@ -67,11 +68,13 @@ + - - + + + From d02d21f9e12abd773e7ce338be7b0ac56d363191 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Thu, 21 Aug 2025 16:45:12 +0530 Subject: [PATCH 08/14] [ADD] estate: added Cancel and Sold buttons to property, Accept/Refuse to offer - Property page now has Cancel and Sold buttons for quick status updates - Cancelled properties cannot be marked sold and vice versa - Offer page includes Accept and Refuse buttons for better offer handling - Accepting an offer sets the buyer and selling price automatically - Ensures only one offer can be accepted per property --- estate/__manifest__.py | 1 + estate/models/estate_property.py | 16 +++++++-- estate/models/estate_property_offer.py | 23 ++++++++++++- estate/models/estate_property_tag.py | 2 +- estate/models/estate_property_type.py | 2 +- estate/security/ir.model.access.csv | 1 - estate/views/estate_menus.xml | 17 +++------- estate/views/estate_property_offer_views.xml | 11 +++---- estate/views/estate_property_tag_views.xml | 3 +- estate/views/estate_property_type_views.xml | 3 +- estate/views/estate_property_views.xml | 34 +++++++++----------- 11 files changed, 65 insertions(+), 48 deletions(-) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 717f226463a..66912de2ca6 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -11,4 +11,5 @@ "views/estate_property_offer_views.xml", "views/estate_menus.xml", ], + "license": "LGPL-3" } diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 70fe2d1cf9e..1757d27c377 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,6 @@ -from odoo import models, fields, api from datetime import timedelta +from odoo import api, fields, models +from odoo.exceptions import UserError class EstateModel(models.Model): @@ -45,7 +46,7 @@ class EstateModel(models.Model): help="Select the direction the garden faces", ) property_type_id = fields.Many2one("estate.property.type") - buyer = fields.Many2one("res.partner") + buyer = fields.Many2one("res.partner", readonly=True) salesman = fields.Many2one("res.users") tag_ids = fields.Many2many("estate.property.tag") offer_ids = fields.One2many("estate.property.offer", inverse_name="property_id") @@ -62,7 +63,6 @@ def _compute_best_price(self): for record in self: record.best_price = max(record.offer_ids.mapped("price"), default=0.0) - # the onchange method will access self which will hold the current form value object not all the value objects @api.onchange("garden") def _set_garden_default_values(self): if self.garden: @@ -71,3 +71,13 @@ def _set_garden_default_values(self): else: self.garden_area = 0 self.garden_orientation = "" + + def action_property_sold(self): + if self.state in ["Sold", "Cancelled"]: + raise UserError(f"Property is already {self.state}") + self.state = "Sold" + + def action_property_cancelled(self): + if self.state in ["Sold", "Cancelled"]: + raise UserError(f"Property is already {self.state}") + self.state = "Cancelled" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9c2dd881c91..4178ceb51ef 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ -from odoo import models, fields, api from datetime import timedelta +from odoo import api, fields, models +from odoo.exceptions import UserError class EstateOfferModel(models.Model): @@ -29,3 +30,23 @@ def _inverse_date_deadline(self): if record.date_deadline else 0 ) + + def action_offer_confirm(self): + for record in self: + if record.status in ["Accepted", "Refused"]: + raise UserError(f"Offer is already {record.status}") + + for offer in record.property_id.offer_ids: + if offer.id == record.id: + record.status = "Accepted" + record.property_id.state = "Offer Accepted" + record.property_id.buyer = record.partner_id + record.property_id.selling_price = record.price + else: + offer.status = "Refused" + + def action_offer_cancel(self): + for record in self: + if record.status in ["Accepted", "Refused"]: + raise UserError(f"Offer is already {record.status}") + record.status = "Refused" diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 293a0f7841b..c8ce45dcb04 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import fields, models class EstateTagModel(models.Model): diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index d04b5ec5f0e..93d06b486e2 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import fields, models class EstateTypeModel(models.Model): diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 47dae2d3b06..30fc332afaf 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -3,4 +3,3 @@ access_estate_property_user,model_estate_property,model_estate_property,base.gro access_estate_property_type_user,model_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 access_estate_property_tag_user,model_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 access_estate_property_offer_user,model_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 - diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index c37ed208a70..c510a6f31f4 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,20 +2,13 @@ - - + - - - - + + + - - \ No newline at end of file + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 9c9a1c59128..091b883bfad 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -11,8 +11,6 @@

- - Estate Properties Offer list estate.property.offer @@ -20,11 +18,12 @@ - - + +

+ + + + - + @@ -65,9 +66,8 @@ - - - + + @@ -87,26 +87,22 @@
- - Estate Property Search estate.property - + - - - \ No newline at end of file + From 5782473e22f321ec2ee3a42a5ea960bfbdab6912 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Fri, 22 Aug 2025 11:51:31 +0530 Subject: [PATCH 09/14] [ADD] estate : add data constraints from prices and uniqueness - Added SQL constraints for positive expected and selling prices, positive offer price, and unique tag/type names. - Implemented Python constraint to ensure selling price is at least 90% of expected price. - Used float_compare() and float_is_zero() for accurate float handling. - Triggered constraint on changes to selling_price and expected_price fields. --- estate/models/estate_property.py | 33 +++++++++++++++++++++++++- estate/models/estate_property_offer.py | 8 +++++++ estate/models/estate_property_tag.py | 4 ++++ estate/models/estate_property_type.py | 8 +++++++ 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 1757d27c377..8f79785b0e2 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,6 +1,7 @@ from datetime import timedelta from odoo import api, fields, models -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError +from odoo.tools.float_utils import float_compare, float_is_zero class EstateModel(models.Model): @@ -53,6 +54,36 @@ class EstateModel(models.Model): total_area = fields.Float(compute="_compute_total_area") best_price = fields.Float(compute="_compute_best_price") + _sql_constraints = [ + ( + "check_property_expected_price", + "CHECK(expected_price > 0)", + "Property Expected Price must be a valid value", + ), + ( + "check_property_selling_price", + "CHECK(selling_price >= 0)", + "Property Selling Price must be positive", + ), + ] + + @api.constrains("selling_price", "expected_price") + def _check_selling_price_percentage(self): + for record in self: + if float_is_zero(record.selling_price, precision_rounding=0.01): + continue + if ( + float_compare( + record.selling_price, + (record.expected_price * 0.9), + precision_rounding=0.01, + ) + < 0 + ): + raise ValidationError( + "Selling Price of the property can not be less than 90 percent of the expected Price" + ) + @api.depends("living_area", "garden_area") def _compute_total_area(self): for record in self: diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 4178ceb51ef..7873cd82f59 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -16,6 +16,14 @@ class EstateOfferModel(models.Model): validity = fields.Integer(default=7) date_deadline = fields.Date(default=fields.Date.today() + timedelta(days=7)) + _sql_constraints = [ + ( + "check_property_offer_price", + "CHECK(price > 0)", + "Property Offer Price must be a valid value", + ) + ] + @api.depends("validity", "create_date") def _compute_date_deadline(self): for record in self: diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index c8ce45dcb04..7144249d62d 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -6,3 +6,7 @@ class EstateTagModel(models.Model): _description = "Real Estate Property Tag Model" name = fields.Char(required=True) + + _sql_constraints = [ + ("check_property_tag_name", "UNIQUE(name)", "Property Tag Name must be unique") + ] diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 93d06b486e2..0346b1d5c96 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -6,3 +6,11 @@ class EstateTypeModel(models.Model): _description = "Real Estate Property Type Model" name = fields.Char(required=True) + + _sql_constraints = [ + ( + "check_property_type_name", + "UNIQUE(name)", + "Property Type Name must be unique", + ) + ] From 1d2d7533b4e26a13415d267962cd7cdf868a8f00 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Fri, 22 Aug 2025 15:53:52 +0530 Subject: [PATCH 10/14] [IMP] estate : add statusbar, helper text and refined sold constrains - status will not change to sold/cancelled unless an offer is accepted. - helper text added to visibility field. - state field is added at the header of form view. --- estate/models/estate_property.py | 35 +++++++++++--------- estate/models/estate_property_offer.py | 30 +++++++++++------ estate/models/estate_property_tag.py | 1 + estate/models/estate_property_type.py | 3 ++ estate/views/estate_property_offer_views.xml | 4 +-- estate/views/estate_property_type_views.xml | 32 ++++++++++++++++++ estate/views/estate_property_views.xml | 3 +- 7 files changed, 78 insertions(+), 30 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8f79785b0e2..45550d38046 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -7,6 +7,7 @@ class EstateModel(models.Model): _name = "estate.property" _description = "Real Estate Advertisement Model" + _order = "id desc" name = fields.Char(required=True) description = fields.Text() @@ -25,15 +26,15 @@ class EstateModel(models.Model): garden_area = fields.Integer() state = fields.Selection( string="state", - default="New", + default="new", required=True, copy=False, 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"), ], ) garden_orientation = fields.Selection( @@ -51,8 +52,8 @@ class EstateModel(models.Model): salesman = fields.Many2one("res.users") tag_ids = fields.Many2many("estate.property.tag") offer_ids = fields.One2many("estate.property.offer", inverse_name="property_id") - total_area = fields.Float(compute="_compute_total_area") - best_price = fields.Float(compute="_compute_best_price") + total_area = fields.Float(compute="_compute_total_area", store=True) + best_price = fields.Float(compute="_compute_best_price", store=True) _sql_constraints = [ ( @@ -97,18 +98,20 @@ def _compute_best_price(self): @api.onchange("garden") def _set_garden_default_values(self): if self.garden: - self.garden_area = 10 - self.garden_orientation = "north" + self.write({"garden_area": 10, "garden_orientation": "north"}) else: - self.garden_area = 0 - self.garden_orientation = "" + self.write({"garden_area": 0, "garden_orientation": ""}) def action_property_sold(self): - if self.state in ["Sold", "Cancelled"]: + if self.state in ["sold", "cancelled"]: raise UserError(f"Property is already {self.state}") - self.state = "Sold" + if float_is_zero(self.selling_price, precision_rounding=0.01): + raise UserError( + "Atleast one offer must be accepted before selling the property" + ) + self.state = "sold" def action_property_cancelled(self): - if self.state in ["Sold", "Cancelled"]: + if self.state in ["sold", "cancelled"]: raise UserError(f"Property is already {self.state}") - self.state = "Cancelled" + self.state = "cancelled" diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7873cd82f59..7f60e621115 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -6,14 +6,18 @@ class EstateOfferModel(models.Model): _name = "estate.property.offer" _description = "Real Estate Property Offer Model" + _order = "price desc" price = fields.Float() status = fields.Selection( - [("Accepted", "Accepted"), ("Refused", "Refused")], copy=False + [("accepted", "Accepted"), ("refused", "Refused")], copy=False ) partner_id = fields.Many2one("res.partner", required=True) property_id = fields.Many2one("estate.property", required=True) - validity = fields.Integer(default=7) + validity = fields.Integer( + default=7, + help="offer validity period in days; the offer will be automatically refused when this expires.", + ) date_deadline = fields.Date(default=fields.Date.today() + timedelta(days=7)) _sql_constraints = [ @@ -41,20 +45,24 @@ def _inverse_date_deadline(self): def action_offer_confirm(self): for record in self: - if record.status in ["Accepted", "Refused"]: + if record.status in ["accepted", "refused"]: raise UserError(f"Offer is already {record.status}") - + # breakpoint() for offer in record.property_id.offer_ids: if offer.id == record.id: - record.status = "Accepted" - record.property_id.state = "Offer Accepted" - record.property_id.buyer = record.partner_id - record.property_id.selling_price = record.price + record.status = "accepted" + record.property_id.write( + { + "state": "offer_accepted", + "buyer": record.partner_id, + "selling_price": record.price, + } + ) else: - offer.status = "Refused" + offer.status = "refused" def action_offer_cancel(self): for record in self: - if record.status in ["Accepted", "Refused"]: + if record.status in ["accepted", "refused"]: raise UserError(f"Offer is already {record.status}") - record.status = "Refused" + record.status = "refused" diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 7144249d62d..d1a0c9ab011 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -4,6 +4,7 @@ class EstateTagModel(models.Model): _name = "estate.property.tag" _description = "Real Estate Property Tag Model" + _order = "name" name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index 0346b1d5c96..c2df8fa7631 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -4,8 +4,11 @@ class EstateTypeModel(models.Model): _name = "estate.property.type" _description = "Real Estate Property Type Model" + _order = "name" name = fields.Char(required=True) + property_ids = fields.One2many("estate.property", "property_type_id") + sequence = fields.Integer("Sequence", default=1, help="Used to change the sequence of Property Types") _sql_constraints = [ ( diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 091b883bfad..0f9df6bea38 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -20,8 +20,8 @@ - + +

+ +

@@ -43,4 +56,5 @@ + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index b59e92ee3df..85f3b0f06a7 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,63 +1,73 @@ + Properties estate.property list,form + {'search_default_available': True}

Create the first property

+ Estate Properties list estate.property - - - - - - - - + + + + + + + + + Estate Property Form estate.property
-
+

- +

- +
+ - + - + + + @@ -72,11 +82,11 @@ - - - - + + + + @@ -84,10 +94,13 @@ +
+
+ Estate Property Search estate.property @@ -97,13 +110,14 @@ - + - + +
From 1065ee5c9b285c573dc3b58d32d9fff8cb81f4ee Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Fri, 29 Aug 2025 10:40:08 +0530 Subject: [PATCH 12/14] [ADD] estate : extend CRUD operations and add property view under user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit . Restrict deletion of properties unless their state is New or Cancelled . Automatically update property state to Offer Received when a new offer is created . Prevent creation of offers lower than existing highest offer . Display salesperson’s available properties directly on the user form . Introduce a new tab in user settings to manage linked properties --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 9 +++++++++ estate/models/estate_property_offer.py | 14 +++++++++++++- estate/models/res_users.py | 11 +++++++++++ estate/views/res_user_view.xml | 18 ++++++++++++++++++ 6 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 estate/models/res_users.py create mode 100644 estate/views/res_user_view.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e619262a5f6..4f9d9c36fbe 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", "views/estate_menus.xml", + "views/res_user_view.xml", ], "license": "LGPL-3", } 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 903c970a6ea..6b9d109b4ac 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -121,3 +121,12 @@ def action_property_cancelled(self): for offer in self.offer_ids: if not offer.status: offer.status = "refused" + + @api.ondelete(at_uninstall=False) + def _check_before_deleting_record(self): + # more than one record can be present in recordset + for record in self: + if record.state in ["offer_received", "offer_accepted", "sold"]: + raise UserError( + "Properties with received offers, accepted offers, or that have been sold cannot be deleted." + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index abc2dcd4d13..3a3c250d1aa 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -58,7 +58,6 @@ def action_offer_confirm(self): self.status = "accepted" self.property_id.write( { - "state": "offer_accepted", "buyer": self.partner_id, "selling_price": self.price, } @@ -77,3 +76,16 @@ def action_offer_cancel(self): if record.status in ["accepted", "refused"]: raise UserError(f"Offer is already {record.status}") record.status = "refused" + + @api.model_create_multi + def create(self, vals): + for record in vals: + property = self.env["estate.property"].browse(record["property_id"]) + + if record.get("price") < property.best_price: + raise UserError( + "The offers price cannot be lower than the existing offers" + ) + + property.state = "offer_received" + return super().create(vals) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..54da05a07a7 --- /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", + inverse_name="salesman", + domain="['|', ('state','=','new'), ('state', '=', 'offer_received')]", + ) diff --git a/estate/views/res_user_view.xml b/estate/views/res_user_view.xml new file mode 100644 index 00000000000..197bd8d6440 --- /dev/null +++ b/estate/views/res_user_view.xml @@ -0,0 +1,18 @@ + + + + + + Inherited User View + res.users + + + + + + + + + + + From cd2b9e2d6ff414f7fb6836547fb4e2a320e13d0e Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Fri, 29 Aug 2025 17:55:51 +0530 Subject: [PATCH 13/14] [ADD] estate: auto invoice creation on property sale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit . Added a new linking module estate_account depending on estate and account. . Invoicing activates only when both modules are installed. . When a property is sold, an invoice with two lines (6% commission + 100 fixed fee) is created for the buyer. . Keeps estate module independent while enabling optional accounting integration following Odoo’s modular design. --- estate/models/estate_property.py | 44 ++++++++++++------------ estate_account/__init__.py | 1 + estate_account/__manifest__.py | 8 +++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 37 ++++++++++++++++++++ 5 files changed, 69 insertions(+), 22 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 6b9d109b4ac..a2ec9dba998 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,22 +9,37 @@ class EstateModel(models.Model): _description = "Real Estate Advertisement Model" _order = "id desc" - name = fields.Char(required=True) - description = fields.Text() - postcode = fields.Char() + active = fields.Boolean(default=True) + bedrooms = fields.Integer(default=2) + best_price = fields.Float(compute="_compute_best_price", store=True, default=0.0) + buyer = fields.Many2one("res.partner", readonly=True) date_availability = fields.Date( copy=False, default=fields.Date.today() + timedelta(days=90), ) - active = fields.Boolean(default=True) + description = fields.Text() 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="Select the direction the garden faces", + ) + living_area = fields.Integer() + name = fields.Char(required=True) + offer_ids = fields.One2many("estate.property.offer", inverse_name="property_id") + postcode = fields.Char() + property_type_id = fields.Many2one("estate.property.type") + salesman = fields.Many2one("res.users") + selling_price = fields.Float(readonly=True, copy=False) state = fields.Selection( string="State", default="new", @@ -38,23 +53,8 @@ class EstateModel(models.Model): ("cancelled", "Cancelled"), ], ) - garden_orientation = fields.Selection( - string="Garden Orientation", - selection=[ - ("north", "North"), - ("south", "South"), - ("east", "East"), - ("west", "West"), - ], - help="Select the direction the garden faces", - ) - property_type_id = fields.Many2one("estate.property.type") - buyer = fields.Many2one("res.partner", readonly=True) - salesman = fields.Many2one("res.users") tag_ids = fields.Many2many("estate.property.tag") - offer_ids = fields.One2many("estate.property.offer", inverse_name="property_id") total_area = fields.Float(compute="_compute_total_area", store=True) - best_price = fields.Float(compute="_compute_best_price", store=True, default=0.0) _sql_constraints = [ ( 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..0901cde9d8a --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,8 @@ +{ + "name": "Real Estate Accounting", + "depends": ["estate", "account"], + "category": "Real Estate", + "data": [], + "installable": True, + "license": "LGPL-3", +} 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..9f9b8f95cf7 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,37 @@ +from odoo import Command, models +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_is_zero + + +class EstatePropertyAccounting(models.Model): + _inherit = "estate.property" + + def action_property_sold(self): + if float_is_zero(self.selling_price, precision_rounding=0.01): + raise UserError( + "Atleast one offer must be accepted before selling the property" + ) + values = { + "partner_id": self.buyer.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create( + { + "name": self.name, + "quantity": 1, + "price_unit": self.selling_price * 0.06, + } + ), + Command.create( + { + "name": "administrative_fees", + "quantity": 1, + "price_unit": 100, + } + ), + ], + } + + self.env["account.move"].create(values) + + return super().action_property_sold() From 992658e298f82da1c96cdd8edc0a837776590831 Mon Sep 17 00:00:00 2001 From: satp-odoo Date: Mon, 1 Sep 2025 14:12:39 +0530 Subject: [PATCH 14/14] [ADD] estate: Add Kanban View for Properties Introduces a Kanban view to visually represent properties using a custom QWeb-powered layout, offering a more intuitive and engaging alternative to the standard list view. Key Features: - Displays property name and key pricing details. - Shows expected price and tags by default. - Displays best offer only when offers exist. - Reveals selling price only when an offer is accepted. - Groups properties by property type by default. - Drag and drop is disabled for data consistency. --- estate/models/estate_property_offer.py | 1 + estate/views/estate_property_views.xml | 45 +++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 3a3c250d1aa..8771ab06b68 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -25,6 +25,7 @@ class EstateOfferModel(models.Model): compute="_compute_date_deadline", inverse="_inverse_date_deadline", default=fields.Date.today() + timedelta(days=7), + store=True ) _sql_constraints = [ diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 85f3b0f06a7..001d756508f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,7 +5,7 @@ Properties estate.property - list,form + kanban,list,form {'search_default_available': True}

@@ -14,13 +14,48 @@ + + Estate Properties kanban + estate.property + + + + + +

+
+ + + +
+ +
+ Expected Price: +
+ +
+ Best Price: +
+ +
+ Selling Price: +
+ +
+ +
+
+ + + +
+
+ Estate Properties list estate.property - + @@ -86,7 +121,7 @@ - +