From e41b7ac428e9626780096eee267c6402d51c9c22 Mon Sep 17 00:00:00 2001 From: hipr Date: Thu, 2 Jan 2025 15:42:47 +0530 Subject: [PATCH 01/13] [ADD] real_estate: added base Add Views and Model : Model : estate_property, estate_property_offer, estate_property_tag, estate_property_type Views: estate_menus, estate_property_offer_views, estate_property_tag_views, estate_property_type_views, estate_property_views --- estate/__init__.py | 1 + estate/__manifest__.py | 25 +++++ estate/data/ir.model.access.csv | 5 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 50 +++++++++ estate/models/estate_property_offer.py | 16 +++ estate/models/estate_property_tag.py | 7 ++ estate/models/estate_property_type.py | 7 ++ estate/views/estate_menus.xml | 12 ++ estate/views/estate_property_offer_views.xml | 17 +++ estate/views/estate_property_tag_views.xml | 21 ++++ estate/views/estate_property_type_views.xml | 20 ++++ estate/views/estate_property_views.xml | 109 +++++++++++++++++++ 13 files changed, 291 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py create mode 100644 estate/data/ir.model.access.csv create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py 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_menus.xml 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 create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__init__.py b/estate/__init__.py new file mode 100644 index 0000000000..9a7e03eded --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 0000000000..63f0ff317f --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,25 @@ +{ + 'name': "Real Estate", + 'version': '1.0', + 'depends': ['base'], + 'author': "Hitesh Prajapati", + 'category': 'tutorials/estate', + 'description': """ + Description text + """, + 'application': True, + 'instalable': True, + + 'data':[ + 'data/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' + ], + 'demo':[ + + ] + +} \ No newline at end of file diff --git a/estate/data/ir.model.access.csv b/estate/data/ir.model.access.csv new file mode 100644 index 0000000000..94af865def --- /dev/null +++ b/estate/data/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_model,access_estate_property_model,estate.model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type_model,access_estate_property_type_model,estate.model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag_model,access_estate_property_tag_model,estate.model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer_model,access_estate_property_offer_model,estate.model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 0000000000..e5f89221b0 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer \ 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 0000000000..29229ec3c4 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,50 @@ +from odoo import models, fields +from datetime import timedelta + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Estate Property" + + name = fields.Char(string="Name", required=True) + description = fields.Text(string="Description") + postcode = fields.Char(string="postcode") + date_availability = fields.Date( + string="Date Availability", + default=lambda self: fields.Date.today() + timedelta(days=90), + copy=False + ) + expected_price = fields.Float(string="Expected Price", required=True) + selling_price = fields.Float(string="Selling Price", readonly=True) + bedrooms = fields.Integer(string="Bedrooms", default=2) + living_area = fields.Integer(string="Living Area") + facades = fields.Integer(string="Facades") + garage = fields.Boolean(string="Garge") + 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" + ) + active = fields.Boolean(string="Active", default=True) + state = fields.Selection( + [ + ('new', 'New'), + ('offer received', 'Offer Received'), + ('offer accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('cancelled', 'Cancelled') + ], + string="Status", + default='new', + required=True + ) + property_type_id =fields.Many2one('estate.property.type',string="Property Type") + salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) + buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) + tag_ids = fields.Many2many('estate.property.tag', string="Tag Name") + offer_ids = fields.One2many('estate.property.offer', "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 0000000000..94726f5f6b --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,16 @@ +from odoo import models, fields + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Estate Property Offer" + + price = fields.Float(string="price") + 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 0000000000..d55c821426 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Estate Property Tags" + + name = fields.Char(string="name", required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 0000000000..144838d91a --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Estate Property Type" + + name = fields.Char(string="Name", required=True) \ 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 0000000000..3266a7cdc1 --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 0000000000..e74d7b5515 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,17 @@ + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml new file mode 100644 index 0000000000..7d0c9b0805 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,21 @@ + + + + + Property Tags + estate.property.tag + list,form + + + + estate.property.tag.list + estate.property.tag + + + + + + + + + diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml new file mode 100644 index 0000000000..5ca4931e16 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,20 @@ + + + + Property Type + estate.property.type + list,form + + + + + estate.property.type.list + estate.property.type + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 0000000000..3a614b20f7 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,109 @@ + + + + Properties + estate.property + list,form + + + + + estate.property + estate.property + + + + + + + + + + + + + + + estate.property + estate.property + + + + + + + + + + + + + + + + + + + + + estate.property + estate.property + +
+ + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ +
From 9c985b0f1acba96c3612d07bc09fdea6b2fad8b2 Mon Sep 17 00:00:00 2001 From: hipr Date: Fri, 3 Jan 2025 16:27:33 +0530 Subject: [PATCH 02/13] [IMP] real_estate: connections in property fields Make Connection in property fields using Computed Field, Inverse Function and onchanges, type object --- estate/models/estate_property.py | 47 +++++++++++++++++++- estate/models/estate_property_offer.py | 41 ++++++++++++++++- estate/views/estate_property_offer_views.xml | 11 +++-- estate/views/estate_property_views.xml | 18 ++++++++ 4 files changed, 109 insertions(+), 8 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 29229ec3c4..19ea04f186 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import models, fields +from odoo import models, fields, api, exceptions from datetime import timedelta class EstateProperty(models.Model): @@ -21,6 +21,7 @@ class EstateProperty(models.Model): garage = fields.Boolean(string="Garge") garden = fields.Boolean(string="Garden") garden_area = fields.Integer(string="Garden Area") + total_area = fields.Float(string="Total Area", compute="_compute_total", store=True) garden_orientation = fields.Selection( [ ('north', 'North'), @@ -47,4 +48,46 @@ class EstateProperty(models.Model): salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) tag_ids = fields.Many2many('estate.property.tag', string="Tag Name") - offer_ids = fields.One2many('estate.property.offer', "property_id") \ No newline at end of file + offer_ids = fields.One2many('estate.property.offer', "property_id", ondelete='restrict') + + best_price = fields.Float(string="Best Price", compute="_best_price", store=True) + + @api.depends("offer_ids") + def _best_price(self): + for record in self: + if record.offer_ids: + record.best_price = max(record.offer_ids.mapped('price')) + else: + record.best_price = 0.0 + + + @api.depends("living_area", "garden_area") + def _compute_total(self): + for record in self: + record.total_area = (record.living_area or 0) + (record.garden_area or 0) + + @api.onchange('garden') + def _onchange_garden(self): + for record in self: + if record.garden: + record.garden_area = 10 + record.garden_orientation = 'north' + else: + record.garden_area = 0 + record.garden_orientation = '' + + def action_to_sold(self): + for record in self: + if record.state == 'cancelled': + raise exceptions.UserError("Cancelled Property can not be sold") + elif record.state == 'new': + self.state = 'sold' + + def action_to_cancel(self): + for record in self: + if record.state == 'new': + record.state = 'cancelled' + + + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 94726f5f6b..49f76aa77a 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,6 @@ -from odoo import models, fields +from odoo import models, fields, api +from datetime import datetime +from dateutil.relativedelta import relativedelta class EstatePropertyOffer(models.Model): _name = "estate.property.offer" @@ -13,4 +15,39 @@ class EstatePropertyOffer(models.Model): copy=False ) partner_id = fields.Many2one('res.partner',required=True) - property_id = fields.Many2one('estate.property', required=True) \ No newline at end of file + property_id = fields.Many2one('estate.property', required=True) + validity = fields.Integer(string="Validity", default=7) + date_deadline = fields.Date( + string="Deadline Date", + compute="_compute_date_deadline", + inverse="_inverse_date_deadline", + default=datetime.today() + ) + + @api.depends("validity") + def _compute_date_deadline(self): + for record in self: + record.date_deadline = datetime.today() + relativedelta( + days=record.validity + ) + + def _inverse_date_deadline(self): + for record in self: + if record.date_deadline: + delta = record.date_deadline - datetime.today().date() + record.validity = delta.days + else: + record.validity = 0 + + def action_confirm(self): + for record in self: + record.status = 'accepted' + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id + + + def action_cancel(self): + for record in self: + record.status = 'refused' + record.property_id.selling_price = record.price + record.property_id.buyer_id = record.partner_id \ 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 e74d7b5515..0de17fda11 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -6,10 +6,13 @@ estate.property.offer - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9ccf44166c..6db556f196 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,11 +4,12 @@ Properties estate.property list,form + {'search_default_available': True, 'search_default_current': True}

- Define new property tag + Define new property

- Use options specified in form to list property tag + Use options specified in form to list property

@@ -18,13 +19,15 @@ estate.property estate.property - + + + - + @@ -39,9 +42,10 @@ - - + + + @@ -58,22 +62,24 @@
-

- + - + @@ -102,13 +108,16 @@ - - + + - + From 30c4e152b6f9166982a4e1ec378ab8164e437adc Mon Sep 17 00:00:00 2001 From: hipr Date: Tue, 7 Jan 2025 19:11:48 +0530 Subject: [PATCH 04/13] [IMP] estate: perform inheritance Perform inheritance in the estate Module, Implement Inheritance to Display Estate Properties in Settings -> users & company -> users -> Mitchell Admin --- estate/__manifest__.py | 2 ++ estate/models/__init__.py | 2 +- estate/models/estate_property.py | 13 +++++++--- estate/models/estate_property_offer.py | 33 +++++++++++++++++--------- estate/models/res_users.py | 13 ++++++++++ estate/views/res_users_views.xml | 16 +++++++++++++ 6 files changed, 64 insertions(+), 15 deletions(-) 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 07fb6bf2f8..e97e7e5559 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -4,6 +4,7 @@ 'depends': ['base'], 'author': "Hitesh Prajapati", 'category': 'tutorials/estate', + 'license': 'LGPL-3', 'description': """ Description text """, @@ -12,6 +13,7 @@ 'data':[ 'data/ir.model.access.csv', + 'views/res_users_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index e5f89221b0..0d7dacfb1d 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1 @@ -from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer \ No newline at end of file +from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, res_users \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index efe71c8a5b..8088b4d1c3 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -45,10 +45,10 @@ class EstateProperty(models.Model): default='new', required=True ) - property_type_id =fields.Many2one('estate.property.type',string="Property Type") + property_type_id =fields.Many2one('estate.property.type',string="Property Type", ondelete="restrict") salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user) buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) - tag_ids = fields.Many2many('estate.property.tag', string="Tag Name") + tag_ids = fields.Many2many('estate.property.tag', string="Tag Name", ondelete="cascade") offer_ids = fields.One2many('estate.property.offer', "property_id") best_price = fields.Float(string="Best Price", compute="_best_price", store=True) @@ -105,4 +105,11 @@ def _check_lower_selling_price(self): raise exceptions.ValidationError("selling Price should be Higher than 90%") - + + @api.ondelete(at_uninstall=False) #For Delete property using inherit the ondelete() + def _unlink_if_property_new_and_canclled(self): # override delete method user can delete user if state is NEW or CANCELLED + for record in self: + if record.state not in ['new', 'cancelled']: + raise exceptions.UserError('Only New and Cancelled property can be delete.') + + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index c8e812a32f..90b9b38ac4 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 from datetime import datetime from dateutil.relativedelta import relativedelta @@ -8,6 +8,9 @@ class EstatePropertyOffer(models.Model): _order = 'price desc' price = fields.Float(string="price") + _sql_constraints = [ + ('price_positive', 'CHECK(price > 0)', 'The offer price must be strictly positive!') + ] status = fields.Selection( [ ('accepted', 'Accepted'), @@ -16,7 +19,7 @@ class EstatePropertyOffer(models.Model): copy=False ) partner_id = fields.Many2one('res.partner',required=True) - property_id = fields.Many2one('estate.property', required=True) + property_id = fields.Many2one('estate.property', required=True, ondelete="restrict") validity = fields.Integer(string="Validity", default=7) date_deadline = fields.Date( string="Deadline Date", @@ -24,9 +27,6 @@ class EstatePropertyOffer(models.Model): inverse="_inverse_date_deadline", default=datetime.today() ) - property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) - - @api.depends("validity") def _compute_date_deadline(self): for record in self: @@ -42,6 +42,9 @@ def _inverse_date_deadline(self): else: record.validity = 0 + property_type_id = fields.Many2one(related='property_id.property_type_id', store=True) + + def action_confirm(self): for record in self: record.status = 'accepted' @@ -49,13 +52,21 @@ def action_confirm(self): record.property_id.buyer_id = record.partner_id record.property_id.state = 'offer accepted' - def action_cancel(self): for record in self: record.status = 'refused' - - + + @api.model_create_multi # Use Decorator Class + def create(self, vals_list): # inherit create method + property_data = self.env['estate.property'].browse(vals_list['property_id']) # Fetch Property data using property_id + if vals_list["price"] < property_data.best_price: # Compare Price with best price + raise exceptions.UserError("Price can be greater than best price") + if property_data.state == 'new': # Change State if it is new Data + property_data.state = 'offer received' - _sql_constraints = [ - ('price_positive', 'CHECK(price > 0)', 'The offer price must be strictly positive!') - ] \ No newline at end of file + return super().create(vals_list) + + + + + \ No newline at end of file diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 0000000000..52164d9174 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,13 @@ +from odoo import models, fields + + +class ResUsers(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many( + comodel_name='estate.property', + inverse_name='salesperson_id', + string='Properties', + domain=[('state','not in',["sold","cancelled"])] + ) + diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 0000000000..935b3640f9 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,16 @@ + + + + + estate.property.user.form + res.users + + + + + + + + + + From c92e2f10394e217d2d812221e1ec3233425821bf Mon Sep 17 00:00:00 2001 From: hipr Date: Wed, 8 Jan 2025 19:17:38 +0530 Subject: [PATCH 05/13] [IMP] estate: add new module(estate_account) and add kanban inherit the method of estate.property Make a kanban view group using property type --- estate/models/estate_property_offer.py | 2 +- estate/views/estate_property_views.xml | 39 +++++++++++++++++++++++- estate/views/res_users_views.xml | 1 + estate_account/__init__.py | 1 + estate_account/__manifest__.py | 20 ++++++++++++ estate_account/models/__init__.py | 1 + estate_account/models/estate_property.py | 38 +++++++++++++++++++++++ 7 files changed, 100 insertions(+), 2 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_offer.py b/estate/models/estate_property_offer.py index 90b9b38ac4..0ac86d519f 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -56,7 +56,7 @@ def action_cancel(self): for record in self: record.status = 'refused' - @api.model_create_multi # Use Decorator Class + @api.model_create_multi # Use Decorator Class def create(self, vals_list): # inherit create method property_data = self.env['estate.property'].browse(vals_list['property_id']) # Fetch Property data using property_id if vals_list["price"] < property_data.best_price: # Compare Price with best price diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 6db556f196..755c7a8be2 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,7 +3,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_available': True, 'search_default_current': True}

@@ -133,4 +133,41 @@ + + + + estate_property + estate.property + + + + + +

+
+ Property Name : + +
+
+ Expected Price : + +
+
+ Best Price : + +
+
+ Selling Price : + +
+
+ +
+
+ + + +
+
+ diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index 935b3640f9..4f334862c8 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -10,6 +10,7 @@ + diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 0000000000..9a7e03eded --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models \ No newline at end of file diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py new file mode 100644 index 0000000000..f0aea28e32 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,20 @@ +{ + 'name': "Real Account", + 'version': '1.0', + 'depends': ['base','estate','account'], + 'author': "Hitesh Prajapati", + 'category': 'tutorials/estate_account', + 'license': 'LGPL-3', + 'description': """ + Description text + """, + 'application': True, + 'instalable': True, + + 'data':[ + ], + 'demo':[ + + ] + +} \ No newline at end of file diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py new file mode 100644 index 0000000000..f4c8fd6db6 --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 0000000000..677a490218 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,38 @@ +from odoo import models, Command, fields + + +class EstateAccount(models.Model): + _inherit = "estate.property" + + def action_to_sold(self): + self.env["account.move"].create( + { + "partner_id": self.buyer_id.id, + "move_type": "out_invoice", + "invoice_date": fields.Date.today(), + "invoice_line_ids": [ + Command.create( + { + "name": self.name, + "quantity": 1, + "price_unit": self.expected_price, + } + ), + Command.create( + { + "name": "Additional Charges", + "quantity": 1, + "price_unit": self.expected_price * 0.06, + } + ), + Command.create( + { + "name": "Administrative Fees", + "quantity": 1, + "price_unit": 100, + } + ), + ], + } + ) + return super().action_to_sold() From ff321f4bb1ff152449a3c720793364a198e809d1 Mon Sep 17 00:00:00 2001 From: hipr Date: Thu, 9 Jan 2025 18:58:06 +0530 Subject: [PATCH 06/13] [IMP] estate: define demo data Add fields using CSV and XML Files --- estate/__manifest__.py | 8 +- estate/data/estate.property.type.csv | 4 + estate/data/estate_property_data.xml | 119 ++++++++++++++++++ estate/data/estate_property_offer_data.xml | 68 ++++++++++ estate/models/estate_property_offer.py | 19 ++- estate/{data => security}/ir.model.access.csv | 0 estate/views/estate_property_views.xml | 64 +++++----- 7 files changed, 237 insertions(+), 45 deletions(-) create mode 100644 estate/data/estate.property.type.csv create mode 100644 estate/data/estate_property_data.xml create mode 100644 estate/data/estate_property_offer_data.xml rename estate/{data => security}/ir.model.access.csv (100%) diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e97e7e5559..e79c955409 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -12,13 +12,17 @@ 'instalable': True, 'data':[ - 'data/ir.model.access.csv', + 'security/ir.model.access.csv', 'views/res_users_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', - 'views/estate_menus.xml' + 'views/estate_menus.xml', + 'data/estate.property.type.csv', + 'data/estate_property_data.xml', + 'data/estate_property_offer_data.xml', + ], 'demo':[ diff --git a/estate/data/estate.property.type.csv b/estate/data/estate.property.type.csv new file mode 100644 index 0000000000..ac0f9a3390 --- /dev/null +++ b/estate/data/estate.property.type.csv @@ -0,0 +1,4 @@ +id,name +property_type_id_1,Residential +property_type_id_2,Commercial +property_type_id_3,Industrial and Land \ No newline at end of file diff --git a/estate/data/estate_property_data.xml b/estate/data/estate_property_data.xml new file mode 100644 index 0000000000..7660ff11bb --- /dev/null +++ b/estate/data/estate_property_data.xml @@ -0,0 +1,119 @@ + + + + + Big Villa + new + A nice and big villa + 12345 + 2020-02-02 + 1600000 + 6 + 100 + 4 + True + True + 100000 + south + + + + + Trailer home + cancelled + Home in a trailer park + 54321 + 1970-01-01 + 100000 + 120000 + 1 + 10 + 4 + False + + + + + + Property 3 + 100 + + + + + + Property new 1 + new + Home in a trailer park + 54321 + 1970-01-01 + 100000 + 120000 + 1 + 10 + 4 + False + + + + + Property 5 + 200 + + + + + + Property 6 + 300 + + + + + + Property 7 + 300 + sold + + + + + Property 8 + 300 + sold + + + + + + + + + diff --git a/estate/data/estate_property_offer_data.xml b/estate/data/estate_property_offer_data.xml new file mode 100644 index 0000000000..430c678883 --- /dev/null +++ b/estate/data/estate_property_offer_data.xml @@ -0,0 +1,68 @@ + + + + + 15 + 1 + 10000 + 14 + + + + 15 + 1 + 1500000 + 14 + + + + 10 + 1 + 1500001 + 14 + + + + + 15 + + 310 + 7 + + + + + 10 + + 320 + 7 + + + + + 15 + + 340 + 7 + + + + + 10 + + 350 + 7 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 0ac86d519f..e9268372cf 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -21,12 +21,7 @@ class EstatePropertyOffer(models.Model): partner_id = fields.Many2one('res.partner',required=True) property_id = fields.Many2one('estate.property', required=True, ondelete="restrict") validity = fields.Integer(string="Validity", default=7) - date_deadline = fields.Date( - string="Deadline Date", - compute="_compute_date_deadline", - inverse="_inverse_date_deadline", - default=datetime.today() - ) + date_deadline = fields.Date( string="Deadline Date", compute="_compute_date_deadline", inverse="_inverse_date_deadline")# default=datetime.today() @api.depends("validity") def _compute_date_deadline(self): for record in self: @@ -58,11 +53,13 @@ def action_cancel(self): @api.model_create_multi # Use Decorator Class def create(self, vals_list): # inherit create method - property_data = self.env['estate.property'].browse(vals_list['property_id']) # Fetch Property data using property_id - if vals_list["price"] < property_data.best_price: # Compare Price with best price - raise exceptions.UserError("Price can be greater than best price") - if property_data.state == 'new': # Change State if it is new Data - property_data.state = 'offer received' + + for vals in vals_list: + property_data = self.env['estate.property'].browse(vals['property_id']) # Fetch Property data using property_id + if vals["price"] < property_data.best_price: # Compare Price with best price + raise exceptions.UserError("Price can be greater than best price") + if property_data.state == 'new': # Change State if it is new Data + property_data.state = 'offer received' return super().create(vals_list) diff --git a/estate/data/ir.model.access.csv b/estate/security/ir.model.access.csv similarity index 100% rename from estate/data/ir.model.access.csv rename to estate/security/ir.model.access.csv diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 755c7a8be2..fa3a03a85f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -135,39 +135,39 @@ - - estate_property - estate.property - - - - - -
-
- Property Name : - -
-
- Expected Price : - -
-
- Best Price : - -
-
- Selling Price : - -
+ + estate_property + estate.property + + + + +
- +
+ Property Name : + +
+
+ Expected Price : + +
+
+ Best Price : + +
+
+ Selling Price : + +
+
+ +
-
-
-
-
-
-
+ + + + + From 9080675b63cc47616aefc3b4b2ce973c277862af Mon Sep 17 00:00:00 2001 From: hipr Date: Fri, 10 Jan 2025 16:56:36 +0530 Subject: [PATCH 07/13] [IMP] estate: build PDF Report in PDF Display Product-wise offers, Display Salesperson-wise Product --- estate/__manifest__.py | 3 +- estate/data/estate_property_data.xml | 5 - estate/models/estate_property.py | 14 +- estate/report/estate_property_reports.xml | 24 ++++ estate/report/estate_property_templates.xml | 144 ++++++++++++++++++++ estate/views/estate_property_views.xml | 2 +- 6 files changed, 178 insertions(+), 14 deletions(-) create mode 100644 estate/report/estate_property_reports.xml create mode 100644 estate/report/estate_property_templates.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e79c955409..df681bd932 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -22,7 +22,8 @@ 'data/estate.property.type.csv', 'data/estate_property_data.xml', 'data/estate_property_offer_data.xml', - + 'report/estate_property_templates.xml', + 'report/estate_property_reports.xml', ], 'demo':[ diff --git a/estate/data/estate_property_data.xml b/estate/data/estate_property_data.xml index 7660ff11bb..b58fe0646e 100644 --- a/estate/data/estate_property_data.xml +++ b/estate/data/estate_property_data.xml @@ -111,9 +111,4 @@ - - - - - diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 8088b4d1c3..c1b43ac5ed 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -54,8 +54,8 @@ class EstateProperty(models.Model): best_price = fields.Float(string="Best Price", compute="_best_price", store=True) sequence = fields.Integer('Sequence', default=0) - @api.depends("offer_ids") - def _best_price(self): + @api.depends("offer_ids") + def _best_price(self): # Commputed Method for record in self: if record.offer_ids: record.best_price = max(record.offer_ids.mapped('price')) @@ -64,11 +64,11 @@ def _best_price(self): @api.depends("living_area", "garden_area") - def _compute_total(self): + def _compute_total(self): # Commputed Method for record in self: record.total_area = (record.living_area or 0) + (record.garden_area or 0) - @api.onchange('garden') + @api.onchange('garden') # Onchange Method def _onchange_garden(self): for record in self: if record.garden: @@ -78,14 +78,14 @@ def _onchange_garden(self): record.garden_area = 0 record.garden_orientation = False - def action_to_sold(self): + def action_to_sold(self): # Function for record in self: if record.state == 'cancelled': raise exceptions.UserError("Cancelled Property can not be sold") elif record.state == 'offer accepted': self.state = 'sold' - def action_to_cancel(self): + def action_to_cancel(self): # Function for record in self: if record.state == 'offer accepted': record.state = 'cancelled' @@ -106,7 +106,7 @@ def _check_lower_selling_price(self): - @api.ondelete(at_uninstall=False) #For Delete property using inherit the ondelete() + @api.ondelete(at_uninstall=False) #For Delete property using inherit the ondelete() def _unlink_if_property_new_and_canclled(self): # override delete method user can delete user if state is NEW or CANCELLED for record in self: if record.state not in ['new', 'cancelled']: diff --git a/estate/report/estate_property_reports.xml b/estate/report/estate_property_reports.xml new file mode 100644 index 0000000000..5d0bdaff63 --- /dev/null +++ b/estate/report/estate_property_reports.xml @@ -0,0 +1,24 @@ + + + + + Print Property Report + estate.property + qweb-pdf + estate.report_property_offers + '%s' % object.name + + report + + + + Salesman reports + res.users + qweb-pdf + estate.report_res_user + '%s' % object.name + + report + + + diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml new file mode 100644 index 0000000000..3d87c7c215 --- /dev/null +++ b/estate/report/estate_property_templates.xml @@ -0,0 +1,144 @@ + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index fa3a03a85f..c585463419 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -49,7 +49,7 @@ - + From df48402d9711390b1b6c7b9b3615dc2b83c0472e Mon Sep 17 00:00:00 2001 From: hipr Date: Mon, 13 Jan 2025 19:07:39 +0530 Subject: [PATCH 08/13] [IMP] estate: complete PDF Reports and improve security in website Make Report using Inherit the template after Improve security: Make a group of users & Managers to access Rights provide Record Rules & check Bypassing Security and Programmatically checking security --- estate/__manifest__.py | 3 +- estate/models/estate_property.py | 1 + estate/report/estate_property_templates.xml | 102 +++++++++++--------- estate/security/ir.model.access.csv | 12 ++- estate/security/security.xml | 42 ++++++++ estate/views/res_users_views.xml | 7 +- estate_account/models/estate_property.py | 4 +- 7 files changed, 114 insertions(+), 57 deletions(-) create mode 100644 estate/security/security.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index df681bd932..07b474a8f5 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -3,7 +3,7 @@ 'version': '1.0', 'depends': ['base'], 'author': "Hitesh Prajapati", - 'category': 'tutorials/estate', + 'category': 'Real Estate/Brokerage', 'license': 'LGPL-3', 'description': """ Description text @@ -12,6 +12,7 @@ 'instalable': True, 'data':[ + 'security/security.xml', 'security/ir.model.access.csv', 'views/res_users_views.xml', 'views/estate_property_offer_views.xml', diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index c1b43ac5ed..9fe36357e3 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -53,6 +53,7 @@ class EstateProperty(models.Model): best_price = fields.Float(string="Best Price", compute="_best_price", store=True) sequence = fields.Integer('Sequence', default=0) + company_id = fields.Many2one('res.company', default=lambda self:self.env.company) @api.depends("offer_ids") def _best_price(self): # Commputed Method diff --git a/estate/report/estate_property_templates.xml b/estate/report/estate_property_templates.xml index 3d87c7c215..0e39caef8c 100644 --- a/estate/report/estate_property_templates.xml +++ b/estate/report/estate_property_templates.xml @@ -1,5 +1,6 @@ + - - \ 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 772ad8b3d7..6a69899609 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -25,5 +25,4 @@ - - \ 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 533f326e17..887b8749f9 100644 --- a/estate/views/estate_property_tag_views.xml +++ b/estate/views/estate_property_tag_views.xml @@ -26,4 +26,4 @@ - \ 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 ae44d30a55..4c115de067 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -61,4 +61,4 @@ - \ No newline at end of file + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 8c37cc6f3c..03f7a88f6b 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -185,4 +185,4 @@ action = records.action_make_offer() - \ No newline at end of file + diff --git a/estate/wizard/__init__.py b/estate/wizard/__init__.py index ae7b9ecb0a..6b65f24122 100644 --- a/estate/wizard/__init__.py +++ b/estate/wizard/__init__.py @@ -1 +1 @@ -from . import estate_property_offer \ No newline at end of file +from . import estate_property_offer diff --git a/estate/wizard/estate_property_offer.py b/estate/wizard/estate_property_offer.py index be4adc3c63..414e707f95 100644 --- a/estate/wizard/estate_property_offer.py +++ b/estate/wizard/estate_property_offer.py @@ -6,11 +6,12 @@ class EstatePropertyOffers(models.TransientModel): price = fields.Float(string="price") partner_id = fields.Many2one('res.partner',required=True) - property_ids = fields.Many2many('estate.property', required=True) validity = fields.Integer(string="Validity", default=7) def make_an_offer(self): - for property in self.property_ids: + active_property_ids = self.env.context.get('active_ids', []) + + for property in self.env['estate.property'].browse(active_property_ids): try: # Create the offer record self.env['estate.property.offer'].create( @@ -23,9 +24,3 @@ def make_an_offer(self): ) except Exception as e: # Catch all exceptions and log them raise exceptions.ValidationError(f"Something went wrong when creating an offer for property {property.id}: {str(e)}") - - - - - - diff --git a/estate/wizard/estate_property_offer.xml b/estate/wizard/estate_property_offer.xml index 5f77b0badf..6cea5c8723 100644 --- a/estate/wizard/estate_property_offer.xml +++ b/estate/wizard/estate_property_offer.xml @@ -8,7 +8,6 @@ -