From e93df221737b59a0388c4ff8942a690e8ece4953 Mon Sep 17 00:00:00 2001 From: rodh-odoo Date: Thu, 3 Jul 2025 14:18:15 +0530 Subject: [PATCH 1/7] [ADD] real_estate: one model table is created estate_property one model created real estate and display in app. one table created for estate property in database with dome database . variable of table like name, description, date etc. --- real_estate/__init__.py | 1 + real_estate/__manifest__.py | 8 ++++++++ real_estate/models/__init__.py | 1 + real_estate/models/estate_property.py | 28 +++++++++++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 real_estate/__init__.py create mode 100644 real_estate/__manifest__.py create mode 100644 real_estate/models/__init__.py create mode 100644 real_estate/models/estate_property.py diff --git a/real_estate/__init__.py b/real_estate/__init__.py new file mode 100644 index 00000000000..9a7e03eded3 --- /dev/null +++ b/real_estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/real_estate/__manifest__.py b/real_estate/__manifest__.py new file mode 100644 index 00000000000..2dc80df9815 --- /dev/null +++ b/real_estate/__manifest__.py @@ -0,0 +1,8 @@ +{ + 'name': 'real_estate', + 'depends': [ + 'base_setup', + ], + 'application': True, + 'installable': True, +} \ No newline at end of file diff --git a/real_estate/models/__init__.py b/real_estate/models/__init__.py new file mode 100644 index 00000000000..f4c8fd6db6d --- /dev/null +++ b/real_estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/real_estate/models/estate_property.py b/real_estate/models/estate_property.py new file mode 100644 index 00000000000..d0c2670c842 --- /dev/null +++ b/real_estate/models/estate_property.py @@ -0,0 +1,28 @@ + +from odoo import fields, models + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate" + + name = fields.Char(string="Title", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="Postcode") + date_availability = fields.Date(string="Available From") + expected_price = fields.Float(string="Expected Price",required=True) + selling_price = fields.Float(string="Selling Price") + bedrooms = fields.Integer(string="Bedrooms") + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garage") + garden = fields.Boolean(string="Garden") + garden_area = fields.Integer(string="Garden Area") + garden_orientation = fields.Selection( + [ + ('north', 'North'), + ('south', 'South'), + ('east', 'East'), + ('west', 'West') + ], + string="Garden Orientation" + ) From 82348a415de93a783d5640c64ab1feed6d0b69f0 Mon Sep 17 00:00:00 2001 From: rodh-odoo Date: Fri, 4 Jul 2025 10:59:28 +0530 Subject: [PATCH 2/7] [ADD] real_estate: add data through csv and xml in list and form view first i add data with csv file in module and after that with xml file code i add data that display on frontend in list view and form view both also disable selling price so anyone cannot change it --- real_estate/__manifest__.py | 9 ++- real_estate/data/estate_menus.xml | 11 +++ real_estate/data/estate_property_views.xml | 91 ++++++++++++++++++++++ real_estate/data/ir.model.access.csv | 2 + real_estate/models/estate_property.py | 24 ++++-- 5 files changed, 131 insertions(+), 6 deletions(-) create mode 100644 real_estate/data/estate_menus.xml create mode 100644 real_estate/data/estate_property_views.xml create mode 100644 real_estate/data/ir.model.access.csv diff --git a/real_estate/__manifest__.py b/real_estate/__manifest__.py index 2dc80df9815..619aa7a9281 100644 --- a/real_estate/__manifest__.py +++ b/real_estate/__manifest__.py @@ -1,8 +1,15 @@ { 'name': 'real_estate', 'depends': [ - 'base_setup', + 'base', ], 'application': True, 'installable': True, + 'license':'LGPL-3', + 'data': [ + 'data/ir.model.access.csv', + 'data/estate_property_views.xml', + 'data/estate_menus.xml', +], + } \ No newline at end of file diff --git a/real_estate/data/estate_menus.xml b/real_estate/data/estate_menus.xml new file mode 100644 index 00000000000..fecfeb47368 --- /dev/null +++ b/real_estate/data/estate_menus.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/real_estate/data/estate_property_views.xml b/real_estate/data/estate_property_views.xml new file mode 100644 index 00000000000..686f0972f4c --- /dev/null +++ b/real_estate/data/estate_property_views.xml @@ -0,0 +1,91 @@ + + + + estate.property.list + estate.property + list + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + Real estate action + estate.property + list,form + +
diff --git a/real_estate/data/ir.model.access.csv b/real_estate/data/ir.model.access.csv new file mode 100644 index 00000000000..976b61e8cb3 --- /dev/null +++ b/real_estate/data/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/real_estate/models/estate_property.py b/real_estate/models/estate_property.py index d0c2670c842..d28af49a28e 100644 --- a/real_estate/models/estate_property.py +++ b/real_estate/models/estate_property.py @@ -1,17 +1,17 @@ - from odoo import fields, models +from datetime import date, timedelta class EstateProperty(models.Model): _name = "estate.property" _description = "Estate" - name = fields.Char(string="Title", required=True) + name = fields.Char(default="Unknown", required=True) description = fields.Text(string="Description") postcode = fields.Char(string="Postcode") - date_availability = fields.Date(string="Available From") + availability_date = fields.Date(default=lambda self: date.today() + timedelta(days=90), copy=False) expected_price = fields.Float(string="Expected Price",required=True) - selling_price = fields.Float(string="Selling Price") - bedrooms = fields.Integer(string="Bedrooms") + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer(string="Living Area") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") @@ -26,3 +26,17 @@ class EstateProperty(models.Model): ], string="Garden Orientation" ) + last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now) + active = fields.Boolean(default=False) + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled'), + ], + required=True, + copy=False, + default='new' + ) From b027653faac2114b4d855814881fafcf411b2740 Mon Sep 17 00:00:00 2001 From: rodh-odoo Date: Mon, 7 Jul 2025 10:12:24 +0530 Subject: [PATCH 3/7] [ADD] real_estate: add business login in estate property and offers for property implement Sold and Cancel button in estate property with validation add offer option in property with accept and refused button automatic update selling price when buyer accept offer only one buyer can accept one offer through validation --- real_estate/__manifest__.py | 10 ++-- real_estate/data/estate_menus.xml | 11 ----- real_estate/data/ir.model.access.csv | 2 - real_estate/models/__init__.py | 5 +- real_estate/models/estate_property.py | 48 +++++++++++++++++-- real_estate/models/estate_property_offer.py | 48 +++++++++++++++++++ real_estate/models/estate_property_tag.py | 7 +++ real_estate/models/estate_property_type.py | 7 +++ real_estate/security/ir.model.access.csv | 5 ++ real_estate/view/estate_menus.xml | 25 ++++++++++ .../view/estate_property_offer_views.xml | 43 +++++++++++++++++ .../view/estate_property_tag_views.xml | 10 ++++ .../view/estate_property_type_views.xml | 11 +++++ .../{data => view}/estate_property_views.xml | 39 ++++++++++++++- 14 files changed, 249 insertions(+), 22 deletions(-) delete mode 100644 real_estate/data/estate_menus.xml delete mode 100644 real_estate/data/ir.model.access.csv create mode 100644 real_estate/models/estate_property_offer.py create mode 100644 real_estate/models/estate_property_tag.py create mode 100644 real_estate/models/estate_property_type.py create mode 100644 real_estate/security/ir.model.access.csv create mode 100644 real_estate/view/estate_menus.xml create mode 100644 real_estate/view/estate_property_offer_views.xml create mode 100644 real_estate/view/estate_property_tag_views.xml create mode 100644 real_estate/view/estate_property_type_views.xml rename real_estate/{data => view}/estate_property_views.xml (64%) diff --git a/real_estate/__manifest__.py b/real_estate/__manifest__.py index 619aa7a9281..2e90223426a 100644 --- a/real_estate/__manifest__.py +++ b/real_estate/__manifest__.py @@ -7,9 +7,13 @@ 'installable': True, 'license':'LGPL-3', 'data': [ - 'data/ir.model.access.csv', - 'data/estate_property_views.xml', - 'data/estate_menus.xml', + 'security/ir.model.access.csv', + 'view/estate_property_views.xml', + + 'view/estate_property_type_views.xml', + 'view/estate_property_tag_views.xml', + 'view/estate_property_offer_views.xml', + 'view/estate_menus.xml', ], } \ No newline at end of file diff --git a/real_estate/data/estate_menus.xml b/real_estate/data/estate_menus.xml deleted file mode 100644 index fecfeb47368..00000000000 --- a/real_estate/data/estate_menus.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/real_estate/data/ir.model.access.csv b/real_estate/data/ir.model.access.csv deleted file mode 100644 index 976b61e8cb3..00000000000 --- a/real_estate/data/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/real_estate/models/__init__.py b/real_estate/models/__init__.py index f4c8fd6db6d..09b2099fe84 100644 --- a/real_estate/models/__init__.py +++ b/real_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/real_estate/models/estate_property.py b/real_estate/models/estate_property.py index d28af49a28e..aab0cd57b8f 100644 --- a/real_estate/models/estate_property.py +++ b/real_estate/models/estate_property.py @@ -1,5 +1,6 @@ -from odoo import fields, models +from odoo import fields, models, api from datetime import date, timedelta +from odoo.exceptions import UserError class EstateProperty(models.Model): _name = "estate.property" @@ -12,11 +13,12 @@ class EstateProperty(models.Model): expected_price = fields.Float(string="Expected Price",required=True) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) - living_area = fields.Integer(string="Living Area") + living_area = fields.Float(string="Living Area") facades = fields.Integer(string="Facades") garage = fields.Boolean(string="Garage") garden = fields.Boolean(string="Garden") - garden_area = fields.Integer(string="Garden Area") + garden_area = fields.Float(string="Garden Area") + total_area = fields.Float(string="Total Area", compute="_compute_total_area") garden_orientation = fields.Selection( [ ('north', 'North'), @@ -40,3 +42,43 @@ class EstateProperty(models.Model): copy=False, default='new' ) + property_type_id = fields.Many2one('estate.property.type', string="Property Type") + buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) + salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) + tag_ids = fields.Many2many('estate.property.tag', string="Tags") + offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers") + best_price = fields.Float(string="Best Offer", 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 or 0.0) + (record.garden_area or 0.0) + + @api.depends('offer_ids.price') + def _compute_best_price(self): + for record in self: + prices = record.offer_ids.mapped('price') + record.best_price = max(prices) if prices else 0.0 + + @api.onchange('garden') + def _onchange_garden(self): + if self.garden: + self.garden_area = 10 + self.garden_orientation = 'north' + else: + self.garden_area = 0 + self.garden_orientation = False + + def action_cancel(self): + for record in self: + if record.state == 'sold': + raise UserError("Sold properties cannot be cancelled.") + record.state = 'cancelled' + return True + + def action_sold(self): + for record in self: + if record.state == 'cancelled': + raise UserError("Cancelled properties cannot be sold.") + record.state = 'sold' + return True \ No newline at end of file diff --git a/real_estate/models/estate_property_offer.py b/real_estate/models/estate_property_offer.py new file mode 100644 index 00000000000..d10328f6d3c --- /dev/null +++ b/real_estate/models/estate_property_offer.py @@ -0,0 +1,48 @@ +from odoo import models, fields, api +from datetime import timedelta +from odoo.exceptions import UserError + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Property Offer" + + price = fields.Float(string="Offer Price") + status = fields.Selection( + [('accepted', 'Accepted'), ('refused', 'Refused')], + string="Status", + copy=False + ) + partner_id = fields.Many2one('res.partner', string="Partner", required=True) + property_id = fields.Many2one('estate.property', string="Property", required=True) + + validity = fields.Integer(default=7) + date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True) + + @api.depends('create_date', 'validity') + def _compute_date_deadline(self): + for record in self: + create_date = record.create_date or fields.Datetime.now() + record.date_deadline = create_date.date() + timedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + create_date = record.create_date or fields.Datetime.now() + delta = record.date_deadline - create_date.date() + record.validity = delta.days + + def action_accept(self): + for offer in self: + if offer.property_id.buyer_id: + raise UserError("An offer has already been accepted.") + offer.status = 'accepted' + offer.property_id.write({ + 'selling_price': offer.price, + 'buyer_id': offer.partner_id.id, + 'state': 'offer_accepted' + }) + return True + + def action_refuse(self): + for offer in self: + offer.status = 'refused' + return True \ No newline at end of file diff --git a/real_estate/models/estate_property_tag.py b/real_estate/models/estate_property_tag.py new file mode 100644 index 00000000000..72126d2f3ea --- /dev/null +++ b/real_estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Property Tag" + + name = fields.Char(string="Name", required=True) diff --git a/real_estate/models/estate_property_type.py b/real_estate/models/estate_property_type.py new file mode 100644 index 00000000000..d3a011880d2 --- /dev/null +++ b/real_estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import models, fields + +class EstatePropertyType(models.Model): + _name = 'estate.property.type' + _description = 'Property Type' + + name = fields.Char(required=True) \ No newline at end of file diff --git a/real_estate/security/ir.model.access.csv b/real_estate/security/ir.model.access.csv new file mode 100644 index 00000000000..8fab5609adf --- /dev/null +++ b/real_estate/security/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_user,access.estate.property.type.user,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_user,access.estate.property.tag.user,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_user,access.estate.property.offer.user,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/real_estate/view/estate_menus.xml b/real_estate/view/estate_menus.xml new file mode 100644 index 00000000000..541225da8da --- /dev/null +++ b/real_estate/view/estate_menus.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/real_estate/view/estate_property_offer_views.xml b/real_estate/view/estate_property_offer_views.xml new file mode 100644 index 00000000000..85edca2a5fb --- /dev/null +++ b/real_estate/view/estate_property_offer_views.xml @@ -0,0 +1,43 @@ + + + + Real estate offer + estate.property.offer + list,form + + + + estate.property.offer.form + estate.property.offer + +
+
+
+ + + + + + + +
+
+
+ + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + + + + + + + + + + + + + + + + + + estate.property.type.tree + estate.property.type + + + + + + + +
diff --git a/real_estate/view/estate_property_views.xml b/real_estate/view/estate_property_views.xml index b0de68d3d34..81ea2327878 100644 --- a/real_estate/view/estate_property_views.xml +++ b/real_estate/view/estate_property_views.xml @@ -1,16 +1,21 @@ + estate.property.list estate.property - list - + + + @@ -27,20 +32,26 @@
-
+