From f798314c3b80c8c423bbcac73c2618602ad09d19 Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Thu, 12 Dec 2024 10:52:19 +0400 Subject: [PATCH 01/14] [ADD] estate: add new module app named as estate Created new module to add another app named as estate for real estate operations. It contains summary, description, depends, and other values as well. Chapter 2 of the server framework 101 --- estate/__init__.py | 0 estate/__manifest__.py | 20 ++++++++++++++++++++ 2 files changed, 20 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 0000000000..e69de29bb2 diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 0000000000..0f60a35e85 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,20 @@ +{ + 'name': 'Estate', + 'summary': """ + Starting module for "Server framework 101, chapter 2: Build an estate app" + """, + + 'description': """ + Starting module for "Server framework 101, chapter 2: Build an estate app" + """, + + 'author': "Odoo", + 'website': "https://www.odoo.com/", + 'category': 'Tutorials/Estate', + 'version': '0.1', + 'application': True, + 'installable': True, + 'depends': ['base', 'web'], + + 'data': [] +} From cc0134c61499a8cee52b7bf150208f24b53d57ad Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Thu, 12 Dec 2024 15:22:29 +0400 Subject: [PATCH 02/14] [IMP] estate: Created a property model for estate module. Created a new model called property to store the details of any property that comes in the database. It has all the fields which are required for a property so that nothing gets missed while publishing a property. --- estate/__init__.py | 4 ++++ estate/__manifest__.py | 7 ++++++- estate/models/__init__.py | 4 ++++ estate/models/estate_property.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) 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 e69de29bb2..e9917144f6 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models \ No newline at end of file diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 0f60a35e85..3b171dff8c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + { 'name': 'Estate', 'summary': """ @@ -16,5 +19,7 @@ 'installable': True, 'depends': ['base', 'web'], - 'data': [] + 'data': [], + + 'license': 'AGPL-3' } diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 0000000000..0c4a347c60 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +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 0000000000..86ce3ffe03 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + +class Property(models.Model): + _name = "estate.property" + _description = "Estate property details" + + name = fields.Char(string='Name of the property', required=True, default='Property Name') + description = fields.Text(string='Description of the property') + + postcode = fields.Char(string='Postal code') + + date_availability = fields.Date(string='Availability date') + expected_price = fields.Float(string='Expected price', required=True, default=0.0) + selling_price = fields.Float(string='Selling Price') + + bedrooms = fields.Integer(string='Number of bedrooms') + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), + ('east', 'East'), ('west', 'West')]) + From 88be25ad74ca03441c8f033d220e08486526c5bd Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Thu, 12 Dec 2024 17:38:00 +0400 Subject: [PATCH 03/14] [IMP] estate: Added access rights for the models in estate module. Completed Chapter 4 - Security and gave all access rights to the base user group. Now I am not getting any log warning for access rights. Created a new csv file in the security directory and defined it in the manifest file right in the data tag. --- estate/__manifest__.py | 4 +++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 3b171dff8c..e380c38586 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -19,7 +19,9 @@ 'installable': True, 'depends': ['base', 'web'], - 'data': [], + 'data': [ + 'security/ir.model.access.csv', + ], 'license': 'AGPL-3' } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 0000000000..9ccf73e191 --- /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,estate.property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From e992eb555eb4580c0ae4d7dfa974865ef517c213 Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Sun, 15 Dec 2024 15:47:24 +0400 Subject: [PATCH 04/14] [IMP] estate: Configured views for module and property model. Completed chapter 5 of server framework 101. Added views for Estate app and property model itself using the default views. Explored actions, menuitems, XML, usage of different type of fields and attributes. --- estate/__manifest__.py | 2 ++ estate/models/estate_property.py | 22 ++++++++++++++++------ estate/views/estate_menu_views.xml | 9 +++++++++ estate/views/estate_property_views.xml | 12 ++++++++++++ 4 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 estate/views/estate_menu_views.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e380c38586..c5fb1a5c83 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -21,6 +21,8 @@ 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menu_views.xml' ], 'license': 'AGPL-3' diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 86ce3ffe03..f0b8cddc58 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,21 +2,24 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import models, fields +from datetime import timedelta class Property(models.Model): _name = "estate.property" _description = "Estate property details" - name = fields.Char(string='Name of the property', required=True, default='Property Name') - description = fields.Text(string='Description of the property') + name = fields.Char(string='Name', required=True) + description = fields.Text(string='Description') postcode = fields.Char(string='Postal code') - date_availability = fields.Date(string='Availability date') - expected_price = fields.Float(string='Expected price', required=True, default=0.0) - selling_price = fields.Float(string='Selling Price') + date_availability = fields.Date(string='Availability Date', copy=False, + default=fields.Date.today()+timedelta(days=90)) + + expected_price = fields.Float(string='Expected Price', required=True, default=0.0) + selling_price = fields.Float(string='Selling Price', readonly=True, copy=False) - bedrooms = fields.Integer(string='Number of bedrooms') + bedrooms = fields.Integer(string='Number of bedrooms', default=2) living_area = fields.Integer() facades = fields.Integer() garage = fields.Boolean() @@ -25,4 +28,11 @@ class Property(models.Model): garden_area = fields.Integer() garden_orientation = fields.Selection([('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]) + + active = fields.Boolean(default=True) + state = fields.Selection([('new', 'New'), ('offer received', 'Offer Received'), + ('offer accpeted', 'Offer Accepted'), ('cancelled', 'Cancelled')], + default="new", + required=True, + copy=False) diff --git a/estate/views/estate_menu_views.xml b/estate/views/estate_menu_views.xml new file mode 100644 index 0000000000..2dc0d26b25 --- /dev/null +++ b/estate/views/estate_menu_views.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 0000000000..e2fac300a9 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,12 @@ + + + + + Properties + estate.property + list,form + +

Define a new property

+
+
+
\ No newline at end of file From bf95f9b892906035f1dbce7cd11fceb0ce9ac963 Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Mon, 16 Dec 2024 10:27:50 +0400 Subject: [PATCH 05/14] [IMP] estate: Created custom list, form, and search views for properties. While practicing chapter no 6 for framework 101 created these: Created custom form view for the properties. Created filter for the available product using OR operator. Created a group by for postcode. --- estate/views/estate_property_views.xml | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index e2fac300a9..d456f02af5 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,6 +1,84 @@ + + + + estate.property.search + estate.property + + + + + + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + estate.property.list + estate.property + + + + + + + + + + + + + + Properties estate.property From f2f36e4ed3b3a30409e323d04d801b461eefdbba Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Mon, 16 Dec 2024 16:06:11 +0400 Subject: [PATCH 06/14] [IMP] estate: Configured relations between property, offers, tag, and type models. Practiced linking different models using all kind of relations. Created new models and views for types, tags, offers and linked them with property model. Added new atributes and foreign keys in property model and views. --- estate/__manifest__.py | 3 ++ estate/models/__init__.py | 5 ++- estate/models/estate_property.py | 30 ++++++++++++++++++ estate/models/estate_property_offer.py | 26 ++++++++++++++++ estate/models/estate_property_tag.py | 10 ++++++ estate/models/estate_property_type.py | 10 ++++++ estate/security/ir.model.access.csv | 5 ++- estate/views/estate_menu_views.xml | 4 +++ estate/views/estate_property_offer_views.xml | 32 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 12 ++++++++ estate/views/estate_property_type_views.xml | 14 +++++++++ estate/views/estate_property_views.xml | 12 ++++++++ 12 files changed, 161 insertions(+), 2 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 c5fb1a5c83..2585414cb6 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -22,6 +22,9 @@ '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_menu_views.xml' ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 0c4a347c60..18dbe7ac06 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -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 f0b8cddc58..2085d9e289 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -36,3 +36,33 @@ class Property(models.Model): default="new", required=True, copy=False) + + property_type_id = fields.Many2one( + 'estate.property.type', + string="Type", + help="Select the category for this product" + ) + + property_seller_id = fields.Many2one( + 'res.users', + string='Salesperson', + default=lambda self: self.env.user + ) + + property_buyer_id = fields.Many2one( + 'res.partner', + string='Buyer', + copy=False + ) + + property_tag_ids = fields.Many2many( + 'estate.property.tag', + copy=False + ) + + offer_ids = fields.One2many( + comodel_name='estate.property.offer', + inverse_name='property_id', + string="Offers", + copy=False + ) \ 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..7e6e23eedc --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + +class PropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Manage different offers from buyers for a specific property." + + price = fields.Float(string="Price", required=True) + + status = fields.Selection([('accepted', 'Accepted'), + ('refused', 'Refused')], + copy=False) + + partner_id = fields.Many2one( + 'res.partner', + string='Buyer', + required=True + ) + + property_id = fields.Many2one( + 'estate.property', + string='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..98eee52610 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + +class PropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Add different tags to a property like cozy, spacious, etc." + + 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..edfcbaf949 --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + +class PropertyType(models.Model): + _name = "estate.property.type" + _description = "Used to describe the type of the property i.e House, Apartment, etc." + + name = fields.Char(string="Name", 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 9ccf73e191..4e660d2625 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,estate.property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property,estate.property,model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/estate/views/estate_menu_views.xml b/estate/views/estate_menu_views.xml index 2dc0d26b25..aa67815fbe 100644 --- a/estate/views/estate_menu_views.xml +++ b/estate/views/estate_menu_views.xml @@ -5,5 +5,9 @@ + + + +
\ No newline at end of file diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 0000000000..47b7667c16 --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,32 @@ + + + + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + + estate.property.offer.form + 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 0000000000..17fd71cb21 --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,12 @@ + + + + + Tags + estate.property.tag + list,form + +

Define a new 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 0000000000..ef53604491 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,14 @@ + + + + + + + Types + estate.property.type + list,form + +

Define a new 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 d456f02af5..808fd2fdfb 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -32,7 +32,9 @@ + + @@ -55,6 +57,15 @@ + + + + + + + + + @@ -68,6 +79,7 @@ + From da5c20e566efae7c36e0d3f133e3b771727ae110 Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Tue, 17 Dec 2024 10:06:54 +0400 Subject: [PATCH 07/14] [IMP] estate: Added computed fields in estate module. Added new field in the property model to compute the best offer ever givn to it. Created a method to compute the total area of the property based on garden area and living area. Created an onchange method as well for the garden checkbox. Property offer will now have a validity and deadline date which depend on each other. Completed chpter no 8 of server framework101. --- estate/models/estate_property.py | 30 ++++++++++++++++++-- estate/models/estate_property_offer.py | 20 +++++++++++-- estate/views/estate_property_offer_views.xml | 4 +++ estate/views/estate_property_views.xml | 2 ++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2085d9e289..3c6d49c72e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, fields +from odoo import models, fields, api from datetime import timedelta class Property(models.Model): @@ -18,6 +18,8 @@ class Property(models.Model): expected_price = fields.Float(string='Expected Price', required=True, default=0.0) selling_price = fields.Float(string='Selling Price', readonly=True, copy=False) + best_price = fields.Float(string='Best Offer', readonly=True, copy=False, + compute="_compute_best_offer") bedrooms = fields.Integer(string='Number of bedrooms', default=2) living_area = fields.Integer() @@ -32,10 +34,13 @@ class Property(models.Model): active = fields.Boolean(default=True) state = fields.Selection([('new', 'New'), ('offer received', 'Offer Received'), - ('offer accpeted', 'Offer Accepted'), ('cancelled', 'Cancelled')], + ('offer accepted', 'Offer Accepted'), ('cancelled', 'Cancelled')], default="new", required=True, copy=False) + + total_area = fields.Integer(string='Total Area (sqm)', compute="_compute_total_area", + readonly=True, copy=False) property_type_id = fields.Many2one( 'estate.property.type', @@ -65,4 +70,23 @@ class Property(models.Model): inverse_name='property_id', string="Offers", copy=False - ) \ No newline at end of file + ) + + @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_offer(self): + for record in self: + record.best_price = max(record.offer_ids.mapped('price')) + + @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 = '' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7e6e23eedc..5d509260a6 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, fields +from odoo import models, fields, api +from datetime import timedelta, datetime class PropertyOffer(models.Model): _name = "estate.property.offer" @@ -9,6 +10,10 @@ class PropertyOffer(models.Model): price = fields.Float(string="Price", required=True) + validity = fields.Integer(string="Validity (days)", default=7) + date_deadline = fields.Date(string="Deadline", compute="_compute_deadline", + inverse="_inverse_deadline") + status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], copy=False) @@ -23,4 +28,15 @@ class PropertyOffer(models.Model): 'estate.property', string='Property', required=True - ) \ No newline at end of file + ) + + @api.depends("validity", "create_date") + def _compute_deadline(self): + for record in self: + create_date = record.create_date if record.create_date else datetime.now() + record.date_deadline = create_date + timedelta(days=record.validity) + + def _inverse_deadline(self): + for record in self: + create_date = record.create_date if record.create_date else datetime.now() + record.validity = (record.date_deadline - create_date.date()).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 47b7667c16..a401c46127 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -10,6 +10,8 @@ + + @@ -24,6 +26,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 808fd2fdfb..4fe7306b90 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -40,6 +40,7 @@ + @@ -55,6 +56,7 @@ + From ccbac16b21fa3c5183cf07a09d71635b6a8c486a Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Tue, 17 Dec 2024 14:40:17 +0400 Subject: [PATCH 08/14] [IMP] estate: Created buttons for property selling and offer accepting. Performed the following operations while doing chapter no 9 of server framework 101: Added 2 buttons for selling or cancelling the property and upon the clicking of these buttons some fields are also updating. Added 2 buttons in the offers view to accpet or reject an offer and then change some values of property model accordingly. --- estate/models/estate_property.py | 21 +++++++++++++-- estate/models/estate_property_offer.py | 27 ++++++++++++++++++++ estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_tag_views.xml | 1 + estate/views/estate_property_views.xml | 6 ++++- 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 3c6d49c72e..c77a45f116 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -3,6 +3,7 @@ from odoo import models, fields, api from datetime import timedelta +from odoo.exceptions import UserError class Property(models.Model): _name = "estate.property" @@ -34,7 +35,8 @@ class Property(models.Model): active = fields.Boolean(default=True) state = fields.Selection([('new', 'New'), ('offer received', 'Offer Received'), - ('offer accepted', 'Offer Accepted'), ('cancelled', 'Cancelled')], + ('offer accepted', 'Offer Accepted'), ('cancelled', 'Cancelled'), + ('sold', 'Sold')], default="new", required=True, copy=False) @@ -80,7 +82,7 @@ def _compute_total_area(self): @api.depends("offer_ids.price") def _compute_best_offer(self): for record in self: - record.best_price = max(record.offer_ids.mapped('price')) + record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0 @api.onchange("garden") def _onchange_garden(self): @@ -90,3 +92,18 @@ def _onchange_garden(self): else: self.garden_area = 0 self.garden_orientation = '' + + def action_cancel_the_property(self): + for record in self: + if record.state == 'sold': + raise UserError("Sold properties cannot be cancelled.") + record.state = 'cancelled' + return True + + def action_sell_the_property(self): + for record in self: + if record.state == 'cancelled': + raise UserError("Cancelled properties cannot be sold.") + else: + record.state = 'sold' + return True \ No newline at end of file diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 5d509260a6..db2130c847 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -3,6 +3,7 @@ from odoo import models, fields, api from datetime import timedelta, datetime +from odoo.exceptions import UserError class PropertyOffer(models.Model): _name = "estate.property.offer" @@ -40,3 +41,29 @@ def _inverse_deadline(self): for record in self: create_date = record.create_date if record.create_date else datetime.now() record.validity = (record.date_deadline - create_date.date()).days + + def accept_offer(self): + for record in self: + if record.status == 'accepted': + raise UserError('This offer has already been accepted.') + + offer_prop = record.property_id + accepted_off = offer_prop.offer_ids.filtered(lambda x: x.status == 'accepted') + + if accepted_off: + raise UserError('This property has already accepted an offer.') + + offer_prop.property_buyer_id = record.partner_id + offer_prop.selling_price =record.price + + record.status = 'accepted' + return True + + def reject_offer(self): + for record in self: + if record.status == 'accepted': + print("Yes") + record.property_id.property_buyer_id = False + record.property_id.selling_price = False + record.status = 'refused' + return True \ 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 a401c46127..2c429215c3 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -12,6 +12,8 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 224070f53a..dba94cbefd 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -12,10 +12,10 @@ - + - + @@ -30,16 +30,16 @@
-
- + - - + @@ -58,18 +58,18 @@ - - + + - + - - + + @@ -83,7 +83,7 @@ estate.property.list estate.property - + @@ -91,7 +91,8 @@ - + + @@ -101,6 +102,7 @@ Properties estate.property list,form + {'search_default_available': True}

Define a new property

From 13af0cd71d82e0457b73b28e81cdfab04b483b3e Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Wed, 18 Dec 2024 17:52:27 +0400 Subject: [PATCH 11/14] [IMP] estate: Added inline view for properties in type model --- estate/views/estate_property_type_views.xml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml index 6cafa95f53..79723a70f0 100644 --- a/estate/views/estate_property_type_views.xml +++ b/estate/views/estate_property_type_views.xml @@ -37,9 +37,13 @@ + - + + + + From 9fd32a3ea4f6611e7f6412ad18f159066d7a7476 Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Thu, 19 Dec 2024 14:43:24 +0400 Subject: [PATCH 12/14] [IMP] estate: Applied inheritances in estate module in model, view, and classic python inheritance. Applied following things while working on server framework 101: Applied Python inheritance to update the crud operations, specially create and unlink. Applied model level inheritance to extend res.users model and add another field. Applied view level inheritance to extend the view of users. --- estate/__manifest__.py | 1 + estate/models/__init__.py | 3 ++- estate/models/estate_property.py | 14 ++++++++++++++ estate/models/estate_property_offer.py | 17 +++++++++++++++++ estate/models/res_users.py | 13 +++++++++++++ estate/views/res_users_views.xml | 16 ++++++++++++++++ 6 files changed, 63 insertions(+), 1 deletion(-) 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 2585414cb6..5d0912f3af 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -21,6 +21,7 @@ 'data': [ 'security/ir.model.access.csv', + 'views/res_users_views.xml', 'views/estate_property_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 18dbe7ac06..63e14b177a 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -4,4 +4,5 @@ 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 +from . import res_users \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index ce7ea9ed41..83a79cc73e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -80,6 +80,13 @@ class Property(models.Model): copy=False ) + @api.ondelete(at_uninstall=False) + def _unlink_if_new_cancelled(self): + for record in self: + if record.state not in ['new', 'cancelled']: + raise UserError('Only new and cancelled properties can be deleted.') + + @api.depends("living_area", "garden_area") def _compute_total_area(self): for record in self: @@ -131,3 +138,10 @@ def action_sell_the_property(self): else: record.state = 'sold' return True + + def set_property_state(self): + """ + Set the state of a property to 'offer recieved'. + Will be called when an offer is created for a property. + """ + self.state = 'offer received' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7727872fb9..f625143999 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -41,6 +41,23 @@ class PropertyOffer(models.Model): store=True ) + @api.model_create_multi + def create(self, vals): + for record in vals: + if record.get('property_id'): + curr_prop = self.env['estate.property'].browse(record['property_id']) + + if curr_prop.offer_ids: + max_offer = max(curr_prop.offer_ids, key=lambda offer: offer.price) + + if record['price'] < max_offer.price: + raise UserError(f"The offer must be higher then {max_offer.price}") + + else: + curr_prop.set_property_state() + + return super().create(vals) + @api.depends("validity", "create_date") def _compute_deadline(self): for record in self: diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 0000000000..dcaf7af71d --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + +class Users(models.Model): + _inherit = 'res.users' + + property_ids = fields.One2many( + comodel_name='estate.property', + inverse_name='property_seller_id', + domain=['|', ('state', '=', 'new'), ('state', '=', 'offer received')] + ) \ No newline at end of file diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 0000000000..660d3d3230 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,16 @@ + + + + + res.users.view.form.estate.inherit + res.users + + + + + + + + + + \ No newline at end of file From 00bbdd8a5d5754d39aed91feb0f260de58ecfd44 Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Sun, 22 Dec 2024 12:08:32 +0400 Subject: [PATCH 13/14] [IMP] estate: Linked estate with account module to create an invoice when a property is sold. Completed ch 13 of server framework 101. Created a new module 'estate_account' which will be used as a link between estate and account modules. Extended estate_property model to extend action_sold to add invoice creation. --- estate/models/estate_property_offer.py | 1 + estate/views/res_users_views.xml | 1 + estate_account/__init__.py | 4 +++ estate_account/__manifest__.py | 14 +++++++++ estate_account/models/__init__.py | 4 +++ estate_account/models/estate_property.py | 40 ++++++++++++++++++++++++ 6 files changed, 64 insertions(+) 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 f625143999..86960a1108 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -88,6 +88,7 @@ def accept_offer(self): offer_prop.property_buyer_id = record.partner_id offer_prop.selling_price =record.price + offer_prop.state = 'offer accepted' record.status = 'accepted' return True diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml index 660d3d3230..725d7f951b 100644 --- a/estate/views/res_users_views.xml +++ b/estate/views/res_users_views.xml @@ -5,6 +5,7 @@ res.users.view.form.estate.inherit res.users + extension diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 0000000000..e9917144f6 --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +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..01edbc1cc2 --- /dev/null +++ b/estate_account/__manifest__.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'Estate Account', + + 'application': True, + 'installable': True, + + 'depends': ['base', 'web', 'estate', 'account'], + 'category': 'Tutorials/EstateAccount', + + 'license': 'AGPL-3' +} \ 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..0c4a347c60 --- /dev/null +++ b/estate_account/models/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +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..e9abb3bc55 --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models + +class Property(models.Model): + _inherit = "estate.property" + + def action_sell_the_property(self): + print("this is estate account") + + move_val = { + 'move_type': 'out_invoice', + 'partner_id': self.property_buyer_id.id, + 'invoice_line_ids': [ + (0, 0, { + 'name': self.name, + 'quantity': 1.0, + 'price_unit': self.selling_price + } + ), + (0, 0, { + 'name': 'Commission', + 'quantity': 1.0, + 'price_unit': self.selling_price * 0.06 + } + ), + (0, 0, { + 'name': 'Administrative Fee', + 'quantity': 1.0, + 'price_unit': 100.00 + } + ) + ] + } + + move = self.env['account.move'].create(move_val) + + print(move) + return super().action_sell_the_property() \ No newline at end of file From 05e224197cf81498608436a5c0f0c39b4291d73f Mon Sep 17 00:00:00 2001 From: Hammad Arif Date: Sun, 22 Dec 2024 15:46:56 +0400 Subject: [PATCH 14/14] [IMP] estate: Configured kanban view for properties based on their type. While working on server framework 101 ch 14, performed following thing: Created a new kanban view in the property views. Added multiple fields in the card view, some fields are conditional. Kanban view will be divided into groups based upon the property type. User will not be able to drag and drop the cards. --- estate/views/estate_property_views.xml | 30 ++++++++++++++++++++++-- estate_account/models/estate_property.py | 5 ++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index dba94cbefd..872760c1c4 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -97,14 +97,40 @@ + + + estate.property.kanban + estate.property + + + + + +
+ + +

Expected Price:

+ +

Selling Price:

+
+ +

Best Offer:

+
+
+
+
+
+
+
+ Properties estate.property - list,form + list,form,kanban {'search_default_available': True}

Define a new property

- \ No newline at end of file + diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index e9abb3bc55..ee579bdacd 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -7,7 +7,6 @@ class Property(models.Model): _inherit = "estate.property" def action_sell_the_property(self): - print("this is estate account") move_val = { 'move_type': 'out_invoice', @@ -36,5 +35,5 @@ def action_sell_the_property(self): move = self.env['account.move'].create(move_val) - print(move) - return super().action_sell_the_property() \ No newline at end of file + return super().action_sell_the_property() + \ No newline at end of file