From c097d6948dd3d5fe0d976bf2b483e84248c0b22e Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Mon, 16 Dec 2024 16:20:08 +0100 Subject: [PATCH 01/22] [ADD] Create new module for Chapter 1 --- estate/__init__.py | 0 estate/__manifest__.py | 11 +++++++++++ 2 files changed, 11 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..e6a287cac5 --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +{ + 'name': 'Real Estate', + 'version': '1.0', + 'author': "Abdelrhman Fawzy", + 'category': 'Aditi/Real Estate', + 'summary': "Realistic business advertisements", + 'application': True, + 'installable': True, + 'auto_install': False +} From 95b57eb02fd17fe1e49440c84e43489c85712539 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Tue, 17 Dec 2024 11:18:39 +0100 Subject: [PATCH 02/22] [IMP] estate: add ORM table fields --- estate/__init__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 estate/models/__init__.py create mode 100644 estate/models/estate_property.py diff --git a/estate/__init__.py b/estate/__init__.py index e69de29bb2..0650744f6b 100644 --- a/estate/__init__.py +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 0000000000..f4c8fd6db6 --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property \ No newline at end of file diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 0000000000..e8288cf3cb --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields + + +class EstateProperty(models.Model): + _name = "estate_property" + _description = "Real Estate Property Options" + + + name = fields.Char(required=True) + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date(copy=False, default=lambda x: fields.Datetime.add(fields.Datetime.today(), months=3)) + expected_price = fields.Float(required=True) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + garden_orientation = fields.Selection( + selection=[("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")] + ) + active = fields.Boolean(default=True) + state = fields.Selection( + required=True, + copy=False, + default="new", + selection=[("new", "New"), ("offer_received", "Offer received"), ("offer_accepted", "Offer accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")] + ) From 1c040eaacb8b635e61d02227fb2f84a934484d73 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Tue, 17 Dec 2024 11:34:58 +0100 Subject: [PATCH 03/22] [IMP] estate: security permissions added --- estate/__manifest__.py | 6 +++++- estate/security/ir.model.access.csv | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index e6a287cac5..4925f06f6f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -7,5 +7,9 @@ 'summary': "Realistic business advertisements", 'application': True, 'installable': True, - 'auto_install': False + 'auto_install': False, + 'data': [ + 'security/ir.model.access.csv' + ] + } diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 0000000000..85de405deb --- /dev/null +++ b/estate/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 From cc2e4711c2ab910121410975e066afd767e2ed47 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Tue, 17 Dec 2024 13:06:07 +0100 Subject: [PATCH 04/22] [IMP] estate: views added --- estate/__manifest__.py | 4 +++- estate/views/estate_menues.xml | 10 ++++++++++ estate/views/estate_property_views.xml | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 estate/views/estate_menues.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 4925f06f6f..c4fe8a3c2b 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -9,7 +9,9 @@ 'installable': True, 'auto_install': False, 'data': [ - 'security/ir.model.access.csv' + 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menues.xml' ] } diff --git a/estate/views/estate_menues.xml b/estate/views/estate_menues.xml new file mode 100644 index 0000000000..7936254465 --- /dev/null +++ b/estate/views/estate_menues.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 0000000000..c2a0fc920a --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,8 @@ + + + + Properties + estate_property + list,form + + From 2c0d2532381ebbb4a1092e9d0b8bedb42818c31e Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Wed, 18 Dec 2024 12:40:14 +0100 Subject: [PATCH 05/22] [IMP] estate: More views Added --- estate/views/estate_menues.xml | 2 +- estate/views/estate_property_views.xml | 78 ++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/estate/views/estate_menues.xml b/estate/views/estate_menues.xml index 7936254465..33a1e24208 100644 --- a/estate/views/estate_menues.xml +++ b/estate/views/estate_menues.xml @@ -1,7 +1,7 @@ - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index c2a0fc920a..5e6eae4af7 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -1,8 +1,86 @@ + Properties estate_property list,form + + + estate.property.search + estate_property + + + + + + + + + + + + + + + + + + + + + estate.property.list + estate_property + + + + + + + + + + + + + estate.property.form + estate_property + +
+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + +
From 4792d6ac76e68be84eebb59a6d9f7b6d6a1fa62e Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Wed, 18 Dec 2024 16:45:17 +0100 Subject: [PATCH 06/22] [IMP] estate: Models Relationships --- estate/__manifest__.py | 4 ++- estate/models/__init__.py | 5 ++- estate/models/estate_property.py | 13 +++++--- estate/models/estate_property_offer.py | 13 ++++++++ estate/models/estate_property_tag.py | 7 ++++ estate/models/estate_property_type.py | 7 ++++ estate/security/ir.model.access.csv | 3 ++ estate/views/estate_menues.xml | 8 ++++- estate/views/estate_property_offer_views.xml | 35 ++++++++++++++++++++ estate/views/estate_property_tag_views.xml | 23 +++++++++++++ estate/views/estate_property_type_views.xml | 23 +++++++++++++ estate/views/estate_property_views.xml | 33 +++++++++++++----- 12 files changed, 157 insertions(+), 17 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 c4fe8a3c2b..0983e0e69a 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- { 'name': 'Real Estate', 'version': '1.0', @@ -11,6 +10,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_menues.xml' ] diff --git a/estate/models/__init__.py b/estate/models/__init__.py index f4c8fd6db6..2f1821a39c 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ -from . import estate_property \ No newline at end of file +from . import estate_property +from . import estate_property_type +from . import estate_property_tag +from . import estate_property_offer diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e8288cf3cb..98df690d68 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,11 +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 fields, models class EstateProperty(models.Model): - _name = "estate_property" + _name = "estate.property" _description = "Real Estate Property Options" @@ -31,3 +28,9 @@ class EstateProperty(models.Model): default="new", selection=[("new", "New"), ("offer_received", "Offer received"), ("offer_accepted", "Offer accepted"), ("sold", "Sold"), ("cancelled", "Cancelled")] ) + + property_type_id = fields.Many2one(comodel_name="estate.property.type") + user_id = fields.Many2one(comodel_name='res.users', string='Salesperson', default=lambda self: self.env.user) + partner_id = fields.Many2one(comodel_name='res.partner', string='Buyer', copy=False) + property_tag_ids = fields.Many2many(comodel_name='estate.property.tag') + property_offer_ids = fields.One2many('estate.property.offer','property_id') diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 0000000000..7d96f41296 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,13 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = 'estate.property.offer' + _description = 'Offers for real state properties' + + price = fields.Float() + status = fields.Selection( + selection=[("accepted", "Accepted"), ("refused", "Refused")], copy=False + ) + property_id = fields.Many2one(comodel_name='estate.property') + partner_id = fields.Many2one(comodel_name='res.partner', string='Buyer') + diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 0000000000..73e4cbfb12 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,7 @@ +from odoo import fields, models + +class EstatePropertyType(models.Model): + _name = 'estate.property.tag' + _description = 'Tags for real state properties' + + name = fields.Char(requird=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 0000000000..cd89253126 --- /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 = 'Types for real state properties' + + name = fields.Char(requird=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 85de405deb..5d6ae781d3 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -1,2 +1,5 @@ id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 +access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 +access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 +access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menues.xml b/estate/views/estate_menues.xml index 33a1e24208..855e960b03 100644 --- a/estate/views/estate_menues.xml +++ b/estate/views/estate_menues.xml @@ -1,10 +1,16 @@ - + + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 0000000000..856d01101f --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,35 @@ + + + + + Offers + estate.property.offer + list,form + + + + estate.property.offer.list + estate.property.offer + + + + + + + + + + + estate.property.offer.form + 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..ac5f11d20e --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,23 @@ + + + + + Tags + estate.property.tag + list,form + + + + estate.property.tag.form + 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..9ba5abb379 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,23 @@ + + + + + Types + estate.property.type + list,form + + + + estate.property.type.form + estate.property.type + +
+ +

+ +

+
+
+
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 5e6eae4af7..9f28afb9a6 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,13 +3,13 @@ Properties - estate_property + estate.property list,form estate.property.search - estate_property + estate.property @@ -31,7 +31,7 @@ estate.property.list - estate_property + estate.property @@ -40,20 +40,26 @@ + + estate.property.form - estate_property + estate.property
-

- -

+ +

+ +

+ +
+ @@ -63,9 +69,8 @@ - + - @@ -73,8 +78,18 @@ + + + + + + + + + +
From fc1c6e2a00854f8e426e0215434d6118c01e509c Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Thu, 19 Dec 2024 11:33:09 +0100 Subject: [PATCH 07/22] [IMP] Add computed fields --- estate/models/estate_property.py | 25 +++++++++++++++++++- estate/models/estate_property_offer.py | 25 +++++++++++++++++++- estate/models/estate_property_tag.py | 2 +- estate/models/estate_property_type.py | 2 +- estate/views/estate_property_offer_views.xml | 4 ++++ estate/views/estate_property_views.xml | 2 ++ 6 files changed, 56 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 98df690d68..dd20e1762d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,6 @@ -from odoo import fields, models +from odoo import api, fields, models +from datetime import timedelta + class EstateProperty(models.Model): @@ -34,3 +36,24 @@ class EstateProperty(models.Model): partner_id = fields.Many2one(comodel_name='res.partner', string='Buyer', copy=False) property_tag_ids = fields.Many2many(comodel_name='estate.property.tag') property_offer_ids = fields.One2many('estate.property.offer','property_id') + + + total_area = fields.Integer(compute="_compute_total_area") + best_price = fields.Float(compute="_compute_best_price") + + + @api.depends('living_area','garden_area') + def _compute_total_area(self): + for record in self: + record.total_area = record.living_area + record.garden_area + + @api.depends('property_offer_ids.price') + def _compute_best_price(self): + for record in self: + record.best_price = max(record.property_offer_ids.mapped('price'), default=0) + + @api.onchange('garden') + def _onchange_garden(self): + self.garden_area = 10 if self.garden else False + self.garden_orientation = 'n' if self.garden else False + diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 7d96f41296..77765bbfd5 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,9 +1,14 @@ -from odoo import fields, models +from datetime import timedelta, date +from odoo import api, fields, models + class EstatePropertyType(models.Model): _name = 'estate.property.offer' _description = 'Offers for real state properties' + validity = fields.Integer(string="Validity (Days)",default=7) + + price = fields.Float() status = fields.Selection( selection=[("accepted", "Accepted"), ("refused", "Refused")], copy=False @@ -11,3 +16,21 @@ class EstatePropertyType(models.Model): property_id = fields.Many2one(comodel_name='estate.property') partner_id = fields.Many2one(comodel_name='res.partner', string='Buyer') + + date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline') + + @api.depends('validity') + def _compute_date_deadline(self): + for record in self: + if not record.create_date: + record.create_date = fields.Date.today() + record.date_deadline = record.create_date + timedelta(days=record.validity) + + def _inverse_date_deadline(self): + for record in self: + if not record.create_date: + record.create_date = fields.Date.today() + record.validity = (record.date_deadline - record.create_date.date()).days + + + diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py index 73e4cbfb12..d5a3033f88 100644 --- a/estate/models/estate_property_tag.py +++ b/estate/models/estate_property_tag.py @@ -4,4 +4,4 @@ class EstatePropertyType(models.Model): _name = 'estate.property.tag' _description = 'Tags for real state properties' - name = fields.Char(requird=True) + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py index cd89253126..85fec53d19 100644 --- a/estate/models/estate_property_type.py +++ b/estate/models/estate_property_type.py @@ -4,4 +4,4 @@ class EstatePropertyType(models.Model): _name = 'estate.property.type' _description = 'Types for real state properties' - name = fields.Char(requird=True) + name = fields.Char(required=True) diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 856d01101f..51498360a6 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -14,6 +14,8 @@ + +
@@ -27,6 +29,8 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 9f28afb9a6..978508adcb 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -65,6 +65,7 @@ + @@ -78,6 +79,7 @@ + From 09a8058c8e73dc0e13bda7cf612644957316a494 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Thu, 19 Dec 2024 12:51:07 +0100 Subject: [PATCH 08/22] [IMP] Actions Added --- estate/models/estate_property.py | 21 +++++++++++++++++++- estate/models/estate_property_offer.py | 18 ++++++++++++++--- estate/views/estate_property_offer_views.xml | 2 ++ estate/views/estate_property_views.xml | 4 ++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index dd20e1762d..529d87c070 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,5 +1,5 @@ from odoo import api, fields, models -from datetime import timedelta +from odoo.exceptions import UserError @@ -57,3 +57,22 @@ def _onchange_garden(self): self.garden_area = 10 if self.garden else False self.garden_orientation = 'n' if self.garden else False + def action_set_cancelled(self): + for record in self: + if record.state == "cancelled": + raise UserError("Cancelled Items cannot be sold.") + record.state = "sold" + + def action_set_sold(self): + for record in self: + if record.state == "sold": + raise UserError("Sold Items cannot be cancelled.") + record.state = "cancelled" + + + def action_process_accept(self, offer): + if self.state == 'offer_accepted': + raise UserError("this property has already an accepted offer!!") + self.state = 'offer_accepted' + self.selling_price = offer.price + self.partner_id = offer.partner_id diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 77765bbfd5..dcb862129c 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,7 @@ -from datetime import timedelta, date +from datetime import timedelta from odoo import api, fields, models +from odoo.exceptions import UserError + class EstatePropertyType(models.Model): @@ -19,6 +21,18 @@ class EstatePropertyType(models.Model): date_deadline = fields.Date(compute='_compute_date_deadline', inverse='_inverse_date_deadline') + def action_accept(self): + for record in self: + try: + record.property_id.action_process_accept(self) + record.status = 'accepted' + except UserError as e: + raise e + + def action_refuse(self): + for record in self: + record.status = 'refused' + @api.depends('validity') def _compute_date_deadline(self): for record in self: @@ -28,8 +42,6 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for record in self: - if not record.create_date: - record.create_date = fields.Date.today() record.validity = (record.date_deadline - record.create_date.date()).days diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 51498360a6..f1368bc6de 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -16,6 +16,8 @@ + + +
+

+ +

+
+ + + + + + + + + + +
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 0e890a7184..7db708699a 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,6 +5,8 @@ Properties estate.property list,form + {'search_default_available': True} +
@@ -16,7 +18,7 @@ - + @@ -33,15 +35,16 @@ estate.property.list estate.property - + + - + @@ -52,18 +55,19 @@
-

- +
- + @@ -81,8 +85,8 @@ - - + + @@ -94,7 +98,7 @@ - +
From 4e1d2a745d77b5ad9cec9c50d0294043f2eadbf9 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Mon, 23 Dec 2024 08:32:56 +0100 Subject: [PATCH 17/22] [IMP] Inheritance added --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 7 +++++++ estate/models/estate_property_offer.py | 16 +++++++++++++++- estate/models/res_users.py | 12 ++++++++++++ estate/views/res_users_views.xml | 15 +++++++++++++++ 6 files changed, 51 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 11fbf75c08..32931c038c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -14,6 +14,7 @@ "views/estate_property_type_views.xml", "views/estate_property_tag_views.xml", "views/estate_property_offer_views.xml", + 'views/res_users_views.xml', "views/estate_menues.xml", ], "description": "This module allows you to manage real estate properties, property types, tags, offers, and advertisements. It also includes tools for organizing and displaying data effectively.", diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c..9a2189b638 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index bd6f12edc1..a08e077552 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -125,3 +125,10 @@ def action_process_accept(self, offer): self.state = "offer_accepted" self.selling_price = offer.price self.partner_id = offer.partner_id + + + @api.ondelete(at_uninstall=False) + def prevent_delete(self): + for record in self: + if record.state not in ('new','cancelled'): + raise UserError("Error, It CANNOT be deleted") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 97f3367db6..de54a675a1 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,6 +1,6 @@ from datetime import timedelta from odoo import api, fields, models -from odoo.exceptions import UserError +from odoo.exceptions import UserError, ValidationError class EstatePropertyType(models.Model): @@ -50,3 +50,17 @@ def _compute_date_deadline(self): def _inverse_date_deadline(self): for record in self: record.validity = (record.date_deadline - record.create_date.date()).days + + + @api.model_create_multi + def create(self, vals): + property_record = self.env['estate.property'].browse(vals['property_id']) + existing_offers = self.search([ + ('property_id', '=', vals['property_id']) + ]) + if any(offer.price >= vals['price'] for offer in existing_offers): + raise ValidationError( + "Error, You CANNOT create an offer with a lower price than an existing one." + ) + property_record.state = 'offer_received' + return super().create(vals) diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 0000000000..676c2d06c8 --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class Users(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + comodel_name="estate.property", + inverse_name="user_id", + string='Available Properties', + domain=["|", ("state", "=", "new"), ("state", "=", "offer_received")], + ) diff --git a/estate/views/res_users_views.xml b/estate/views/res_users_views.xml new file mode 100644 index 0000000000..7e8857c7e3 --- /dev/null +++ b/estate/views/res_users_views.xml @@ -0,0 +1,15 @@ + + + + res.users.view.form.inherit.estate + res.users + + + + + + + + + + From 77004d5976f3a822bb037ab744b30df904cc7a98 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Mon, 23 Dec 2024 12:41:55 +0100 Subject: [PATCH 18/22] [IMP] Invoicing Linked --- estate_account/__init__.py | 1 + estate_account/manifest.py | 9 +++++++ estate_account/models/__init_.py | 1 + estate_account/models/estate_property.py | 30 +++++++++++++++++++++ estate_account/security/ir.model.access.csv | 2 ++ 5 files changed, 43 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 create mode 100644 estate_account/security/ir.model.access.csv diff --git a/estate_account/__init__.py b/estate_account/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/estate_account/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_account/manifest.py b/estate_account/manifest.py new file mode 100644 index 0000000000..50dfac7539 --- /dev/null +++ b/estate_account/manifest.py @@ -0,0 +1,9 @@ +{ + "name": "Sales Accounting", + "depends": ["estate", "account"], + "installable": True, + "license": "LGPL-3", + "data": [ + "security/ir.model.access.csv", + ], +} diff --git a/estate_account/models/__init_.py b/estate_account/models/__init_.py new file mode 100644 index 0000000000..5e1963c9d2 --- /dev/null +++ b/estate_account/models/__init_.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py new file mode 100644 index 0000000000..9621cc335c --- /dev/null +++ b/estate_account/models/estate_property.py @@ -0,0 +1,30 @@ +from odoo import models, Command + +class InheritedEstateProperty(models.Model): + _inherit = "estate.property" + + def action_set_sold(self): + self.ensure_one() + self.env["account.move"].create( + { + "partner_id": self.buyer_id.id, + "move_type": "out_invoice", + "invoice_line_ids": [ + Command.create( + { + "name": self.name, + "quantity": 1, + "price_unit": 0.06*self.selling_price, + } + ), + Command.create( + { + "name": "Administrative Fees", + "quantity": 1, + "price_unit": 100, + } + ), + ], + } + ) + return super().action_set_sold() diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv new file mode 100644 index 0000000000..85de405deb --- /dev/null +++ b/estate_account/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink +estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 From 2da5257fdd6741c78dc874f917fe634062219adb Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Mon, 23 Dec 2024 13:21:18 +0100 Subject: [PATCH 19/22] [IMP] Kanban View added --- estate/views/estate_property_views.xml | 41 ++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 7db708699a..920ae6ca5c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,7 +4,7 @@ Properties estate.property - list,form + list,form,kanban {'search_default_available': True} @@ -29,8 +29,6 @@ - - estate.property.list estate.property @@ -48,6 +46,43 @@ + + estate.property.kanban + estate.property + + + + + +
+
+
+ + + +
+
+ Expected Price: + +
+
+ Best Offer: + +
+
+ Selling Price: + +
+
+ +
+
+
+
+
+
+
+
estate.property.form estate.property From ac8271952825ad9594b2ac86c0fde1f9852fab64 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Tue, 24 Dec 2024 09:26:36 +0100 Subject: [PATCH 20/22] [IMP] Issues Fix --- estate/models/estate_property.py | 7 ++++++- estate/models/estate_property_offer.py | 13 ++++++++----- estate_account/models/estate_property.py | 2 +- estate_account/security/ir.model.access.csv | 2 -- 4 files changed, 15 insertions(+), 9 deletions(-) delete mode 100644 estate_account/security/ir.model.access.csv diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a08e077552..5fce905e25 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -111,12 +111,16 @@ def action_set_cancelled(self): if self.state == "cancelled": raise UserError("Cancelled Items cannot be sold.") self.state = "sold" + return True + def action_set_sold(self): for record in self: if record.state == "sold": raise UserError("Sold Items cannot be cancelled.") record.state = "cancelled" + return True + def action_process_accept(self, offer): self.ensure_one() @@ -125,10 +129,11 @@ def action_process_accept(self, offer): self.state = "offer_accepted" self.selling_price = offer.price self.partner_id = offer.partner_id + return True @api.ondelete(at_uninstall=False) - def prevent_delete(self): + def _unlink_property(self): for record in self: if record.state not in ('new','cancelled'): raise UserError("Error, It CANNOT be deleted") diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index de54a675a1..3718bd0086 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -32,13 +32,16 @@ class EstatePropertyType(models.Model): ] def action_accept(self): - for record in self: - record.property_id.action_process_accept(record) - record.status = "accepted" + self.ensure_one() + self.property_id.action_process_accept(self) + self.status = "accepted" + return True + def action_refuse(self): - for record in self: - record.status = "refused" + self.ensure_one() + self.status = "refused" + return True @api.depends("validity", "create_date") def _compute_date_deadline(self): diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py index 9621cc335c..e06211186f 100644 --- a/estate_account/models/estate_property.py +++ b/estate_account/models/estate_property.py @@ -1,6 +1,6 @@ from odoo import models, Command -class InheritedEstateProperty(models.Model): +class EstateProperty(models.Model): _inherit = "estate.property" def action_set_sold(self): diff --git a/estate_account/security/ir.model.access.csv b/estate_account/security/ir.model.access.csv deleted file mode 100644 index 85de405deb..0000000000 --- a/estate_account/security/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 -estate.access_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1 From 0445d11ceae3b8ec9ef774e7ba70c4bf27924122 Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Thu, 26 Dec 2024 16:18:12 +0100 Subject: [PATCH 21/22] [IMP] Chapter 1: Web Framework --- .../src/components/TodoItem/todoitem.js | 19 ++++++++ .../src/components/TodoItem/todoitem.xml | 11 +++++ .../src/components/TodoList/todolist.js | 43 +++++++++++++++++++ .../src/components/TodoList/todolist.xml | 14 ++++++ .../static/src/components/card/card.js | 18 ++++++++ .../static/src/components/card/card.xml | 12 ++++++ .../static/src/components/counter/counter.js | 17 ++++++++ .../static/src/components/counter/counter.xml | 10 +++++ awesome_owl/static/src/playground.js | 18 +++++++- awesome_owl/static/src/playground.xml | 43 +++++++++++++++++-- awesome_owl/static/src/utils.js | 9 ++++ 11 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 awesome_owl/static/src/components/TodoItem/todoitem.js create mode 100644 awesome_owl/static/src/components/TodoItem/todoitem.xml create mode 100644 awesome_owl/static/src/components/TodoList/todolist.js create mode 100644 awesome_owl/static/src/components/TodoList/todolist.xml create mode 100644 awesome_owl/static/src/components/card/card.js create mode 100644 awesome_owl/static/src/components/card/card.xml create mode 100644 awesome_owl/static/src/components/counter/counter.js create mode 100644 awesome_owl/static/src/components/counter/counter.xml create mode 100644 awesome_owl/static/src/utils.js diff --git a/awesome_owl/static/src/components/TodoItem/todoitem.js b/awesome_owl/static/src/components/TodoItem/todoitem.js new file mode 100644 index 0000000000..34bccfc468 --- /dev/null +++ b/awesome_owl/static/src/components/TodoItem/todoitem.js @@ -0,0 +1,19 @@ +import { Component} from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.todoitem"; + static props = { + todo: { type: Object }, + toggleState: {type: Function}, + removeTodo: {type: Function} + }; + + toggleState() { + this.props.toggleState(this.props.todo.id); + } + + removeTodo() { + this.props.removeTodo(this.props.todo.id); + } + +} diff --git a/awesome_owl/static/src/components/TodoItem/todoitem.xml b/awesome_owl/static/src/components/TodoItem/todoitem.xml new file mode 100644 index 0000000000..cc0b04323c --- /dev/null +++ b/awesome_owl/static/src/components/TodoItem/todoitem.xml @@ -0,0 +1,11 @@ + + + +
+ + . + + +
+
+
diff --git a/awesome_owl/static/src/components/TodoList/todolist.js b/awesome_owl/static/src/components/TodoList/todolist.js new file mode 100644 index 0000000000..f797986f03 --- /dev/null +++ b/awesome_owl/static/src/components/TodoList/todolist.js @@ -0,0 +1,43 @@ +import { Component, useState} from "@odoo/owl"; +import { TodoItem } from "../TodoItem/todoitem"; +import { useAutofocus } from "../../utils"; + + +export class ToDoList extends Component { + static template = "awesome_owl.todolist"; + static components = { TodoItem }; + setup() { + this.todos = useState([]); + this.todoItemsNr =0; + useAutofocus("todo_input"); + } + addTodo(inp) { + if (inp.keyCode === 13) { + let content = inp.target.value; + if(content){ + const _newTodo = { + id: ++this.todoItemsNr, + description: content, + isCompleted: false + }; + this.todos.push(_newTodo); + + } + inp.target.value = ''; + this.render(); + } + } + + toggleState(id) { + const index = this.todos.findIndex((item) => item.id === id); + this.todos[index].isCompleted = !this.todos[index].isCompleted; + } + + removeTodo(id) { + const index = this.todos.findIndex((item) => item.id === id); + if (index >= 0) { + this.todos.splice(index, 1); + } + } + +} diff --git a/awesome_owl/static/src/components/TodoList/todolist.xml b/awesome_owl/static/src/components/TodoList/todolist.xml new file mode 100644 index 0000000000..cd82af9397 --- /dev/null +++ b/awesome_owl/static/src/components/TodoList/todolist.xml @@ -0,0 +1,14 @@ + + + +
+

Todo List

+
+ +
+
+ +
+
+
+
diff --git a/awesome_owl/static/src/components/card/card.js b/awesome_owl/static/src/components/card/card.js new file mode 100644 index 0000000000..9ed5c0761c --- /dev/null +++ b/awesome_owl/static/src/components/card/card.js @@ -0,0 +1,18 @@ +import { Component, useState} from "@odoo/owl"; + +export class Card extends Component { + static template = "awesome_owl.card"; + static props = { + title: {type: String}, + slots: { type: Object } + } + + setup() { + this.state = useState({ hidden: false }); + } + + toggle() { + this.state.hidden = !this.state.hidden; + } + +} diff --git a/awesome_owl/static/src/components/card/card.xml b/awesome_owl/static/src/components/card/card.xml new file mode 100644 index 0000000000..6d85d12b8a --- /dev/null +++ b/awesome_owl/static/src/components/card/card.xml @@ -0,0 +1,12 @@ + + +
+
+
+ +
+ +
+
+
+
diff --git a/awesome_owl/static/src/components/counter/counter.js b/awesome_owl/static/src/components/counter/counter.js new file mode 100644 index 0000000000..aee34b8e3c --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.js @@ -0,0 +1,17 @@ +import { Component, useState } from "@odoo/owl"; + +export class Counter extends Component { + static template = "awesome_owl.counter"; + static props = { + onChange : {type: Function, optional: true} + } + setup() { + this.state = useState({ + count: 1 + }); + }; + increment() { + this.state.count++; + this.props.onChange?.(); + } +} diff --git a/awesome_owl/static/src/components/counter/counter.xml b/awesome_owl/static/src/components/counter/counter.xml new file mode 100644 index 0000000000..a36a112e32 --- /dev/null +++ b/awesome_owl/static/src/components/counter/counter.xml @@ -0,0 +1,10 @@ + + +
+

Counter:

+
+ +
+
+
+
diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 657fb8b07b..f2d641c051 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,7 +1,23 @@ /** @odoo-module **/ -import { Component } from "@odoo/owl"; +import { Component, markup, useState } from "@odoo/owl"; +import { Counter } from "./components/counter/counter" +import { Card } from "./components/card/card" +import { ToDoList } from "./components/TodoList/todolist" export class Playground extends Component { static template = "awesome_owl.playground"; + static components = { Counter, Card, ToDoList}; + setup() { + this.state = useState({ + sum: 2 + }); + }; + + incrementSum(){ + this.state.sum++; + } + + content1= "
Card with no markup
"; + content2 = markup("
Card with markup content
"); } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f..3717231006 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,45 @@ - -
- hello world +
+
+
+

Counters

+
+
+ +
+
+ +
+
+
+ Sum: +
+
+
+
+
+

Cards

+
+
+ + + +
+
+ + + +
+
+
+
+
+
+ +
+
- diff --git a/awesome_owl/static/src/utils.js b/awesome_owl/static/src/utils.js new file mode 100644 index 0000000000..0b0690f0e9 --- /dev/null +++ b/awesome_owl/static/src/utils.js @@ -0,0 +1,9 @@ +import { useRef, useEffect } from "@odoo/owl" + +export function useAutofocus(itemName) { + let ref = useRef(itemName); + useEffect( + (el) => el?.focus(), + () => [ref.el] + ) +} From 44a16fb905dc7252cf92d0bb30286e2495bfa97f Mon Sep 17 00:00:00 2001 From: abdelrahmanfawzy Date: Fri, 27 Dec 2024 15:54:57 +0100 Subject: [PATCH 22/22] [IMP] Chapter 2 first parts --- awesome_dashboard/__manifest__.py | 1 + awesome_dashboard/static/src/dashboard.js | 10 ------- awesome_dashboard/static/src/dashboard.xml | 8 ------ .../static/src/dashboard/dashboard.js | 26 +++++++++++++++++++ .../static/src/dashboard/dashboard.scss | 3 +++ .../static/src/dashboard/dashboard.xml | 19 ++++++++++++++ .../src/dashboard_items/dahboard_item.xml | 8 ++++++ .../src/dashboard_items/dashboard_item.scss | 7 +++++ .../src/dashboard_items/dashboard_items.js | 6 +++++ 9 files changed, 70 insertions(+), 18 deletions(-) delete mode 100644 awesome_dashboard/static/src/dashboard.js delete mode 100644 awesome_dashboard/static/src/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.scss create mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml create mode 100644 awesome_dashboard/static/src/dashboard_items/dahboard_item.xml create mode 100644 awesome_dashboard/static/src/dashboard_items/dashboard_item.scss create mode 100644 awesome_dashboard/static/src/dashboard_items/dashboard_items.js diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index 9c5a4858df..17e48f7c1f 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -20,6 +20,7 @@ "assets": { "web.assets_backend": [ "awesome_dashboard/static/src/**/*", + "awesome_dashboard/static/src/scss/dashboard.scss", ], }, "license": "AGPL-3", diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index 637fa4bb97..0000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @odoo-module **/ - -import { Component } from "@odoo/owl"; -import { registry } from "@web/core/registry"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard.xml b/awesome_dashboard/static/src/dashboard.xml deleted file mode 100644 index 1a2ac9a2fe..0000000000 --- a/awesome_dashboard/static/src/dashboard.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - hello dashboard - - - diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js new file mode 100644 index 0000000000..683bc4d23c --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,26 @@ +import { useService } from "@web/core/utils/hooks"; +import { Layout } from "@web/search/layout"; +import { DashboardItem } from "../dashboard_items/dashboard_items"; + +export class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem }; + + + setup() { + this.action = useService("action"); + } + + openCustomers() { + this.action.doAction("base.res_partner_action_kanban"); + } + + openLeads() { + this.action.doAction({ + name: "Leads", + type: "ir.actions.act_window", + res_model: "crm.lead", + views: [[false, "list"], [false, "form"]], + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 0000000000..32862ec0d8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,3 @@ +.o_dashboard { + background-color: gray; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 0000000000..9c48b46342 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,19 @@ + + + + + + + + + + Item 1 + Item 2 + + + + + diff --git a/awesome_dashboard/static/src/dashboard_items/dahboard_item.xml b/awesome_dashboard/static/src/dashboard_items/dahboard_item.xml new file mode 100644 index 0000000000..aef49ee9bf --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_items/dahboard_item.xml @@ -0,0 +1,8 @@ + + + +
+ +
+
+
diff --git a/awesome_dashboard/static/src/dashboard_items/dashboard_item.scss b/awesome_dashboard/static/src/dashboard_items/dashboard_item.scss new file mode 100644 index 0000000000..0bed5a9b36 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_items/dashboard_item.scss @@ -0,0 +1,7 @@ +.o_dashboard_item { + background-color: #f4f4f4; + border: 1px solid #ddd; + padding: 1rem; + margin: 0.5rem; + border-radius: 0.5rem; +} diff --git a/awesome_dashboard/static/src/dashboard_items/dashboard_items.js b/awesome_dashboard/static/src/dashboard_items/dashboard_items.js new file mode 100644 index 0000000000..f6063ade10 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_items/dashboard_items.js @@ -0,0 +1,6 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + static props = { size: { type: Number, optional: true, default: 1 } }; +}