From 88181a303d2d15ff4c409d825d451583d16ccef2 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Tue, 10 Mar 2026 18:37:55 +0530 Subject: [PATCH 01/24] [ADD] Chapter 2&3 (Estate) Completed the initial setup for the new Real Estate module. Created the base directory structure and manifest. Successfully installed the module. Initialized model and model fields which generates tables using odoo ORM --- estate/__init__.py | 1 + estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 estate/__init__.py create mode 100644 estate/__manifest__.py 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 new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py new file mode 100644 index 00000000000..606035919fb --- /dev/null +++ b/estate/__manifest__.py @@ -0,0 +1 @@ +{'name': 'Real Estate', 'depends': ['base']} diff --git a/estate/models/__init__.py b/estate/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py new file mode 100644 index 00000000000..2b1f75517b3 --- /dev/null +++ b/estate/models/estate_property.py @@ -0,0 +1,28 @@ +from odoo import fields, models + +class EstateProperty(models.Model): + _name = "estate.property" + _description = "Real Estate Property" # Removes the terminal warning + + # Required fields (not null in database) + name = fields.Char(required=True) + expected_price = fields.Float(required=True) + + # Basic fields + description = fields.Text() + postcode = fields.Char() + date_availability = fields.Date() + selling_price = fields.Float() + bedrooms = fields.Integer() + living_area = fields.Integer() + facades = fields.Integer() + garage = fields.Boolean() + garden = fields.Boolean() + garden_area = fields.Integer() + + # Selection field (Dropdown) + garden_orientation = fields.Selection( + string='Orientation', + selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + help="The direction the garden faces." + ) \ No newline at end of file From 15ba5f87ef0a2c7cdc93cada8d290103aa61eb90 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Wed, 11 Mar 2026 18:01:02 +0530 Subject: [PATCH 02/24] [IMP] Estate:Fixed Issues and Completed Chapter-4 Fixed warnings and errors raised by the first push. Created security/ir.model.access.csv file in estate for defining access rights. Added the data in csv file and defined the csv file in manifest. --- estate/__manifest__.py | 12 +++++++++++- estate/models/estate_property.py | 11 ++++------- estate/security/ir.model.access.csv | 2 ++ 3 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 estate/security/ir.model.access.csv diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 606035919fb..be547d5fdba 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -1 +1,11 @@ -{'name': 'Real Estate', 'depends': ['base']} +{ + 'name': 'Real Estate', + 'author': 'soham', + 'license': 'LGPL-3', + 'depends': ['base'], + 'data': [ + 'security/ir.model.access.csv', + ], + 'installable': True, + 'application': True, +} diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 2b1f75517b3..9556516ad4d 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,14 +1,12 @@ from odoo import fields, models + class EstateProperty(models.Model): _name = "estate.property" - _description = "Real Estate Property" # Removes the terminal warning + _description = "Real Estate Property" - # Required fields (not null in database) name = fields.Char(required=True) expected_price = fields.Float(required=True) - - # Basic fields description = fields.Text() postcode = fields.Char() date_availability = fields.Date() @@ -19,10 +17,9 @@ class EstateProperty(models.Model): garage = fields.Boolean() garden = fields.Boolean() garden_area = fields.Integer() - - # Selection field (Dropdown) garden_orientation = fields.Selection( string='Orientation', selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], help="The direction the garden faces." - ) \ No newline at end of file + + ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv new file mode 100644 index 00000000000..0e11f47e58d --- /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,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file From 1039a390b46dfaaf46f8d4d6ce263006ac13793a Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Thu, 12 Mar 2026 18:38:16 +0530 Subject: [PATCH 03/24] [IMP] Estate:Partially Completed Chapter-5 Created estate_property_views.xml for the window action. Implemented 3-level menu structure: Root, Advertisements, and Properties. Linked the menu to the window action to enable UI navigation. --- estate/__manifest__.py | 2 ++ estate/views/estate_menus.xml | 7 +++++++ estate/views/estate_property_views.xml | 7 +++++++ 3 files changed, 16 insertions(+) create mode 100644 estate/views/estate_menus.xml create mode 100644 estate/views/estate_property_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index be547d5fdba..b6699025785 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,8 @@ 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_views.xml', + 'views/estate_menus.xml', ], 'installable': True, 'application': True, diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml new file mode 100644 index 00000000000..1c6d53ac7bd --- /dev/null +++ b/estate/views/estate_menus.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml new file mode 100644 index 00000000000..4d544c18597 --- /dev/null +++ b/estate/views/estate_property_views.xml @@ -0,0 +1,7 @@ + + + Properties + estate.property + list,form + + From aab2bd161f0f5d40bf88b474de95e244458440ff Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Fri, 13 Mar 2026 19:09:33 +0530 Subject: [PATCH 04/24] [IMP] Estate:Completed Chapter-5 Added readonly and copy=False attributes to selling_price field. Set default value of 2 for bedrooms field. Set default availability date to 3 months from today using Date.add(). Added active reserved field with default=True for record visibility. Added state reserved field with specific values. --- estate/models/estate_property.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 9556516ad4d..6e87ec1cb91 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,10 +9,11 @@ class EstateProperty(models.Model): expected_price = fields.Float(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date() - selling_price = fields.Float() - bedrooms = fields.Integer() + date_availability = fields.Date(copy=False) + selling_price = fields.Float(readonly=True,copy=False) + bedrooms = fields.Integer(default= 2) living_area = fields.Integer() + active = fields.Boolean(default=True) facades = fields.Integer() garage = fields.Boolean() garden = fields.Boolean() @@ -23,3 +24,17 @@ class EstateProperty(models.Model): help="The direction the garden faces." ) + state = fields.Selection( + selection=[ + ('new', 'New'), + ('offer_received', 'Offer Received'), + ('offer_accepted', 'Offer Accepted'), + ('sold', 'Sold'), + ('canceled', 'Cancelled'), + ], + string="Status", + required=True, + copy=False, + default='new', + ) + \ No newline at end of file From ae34671000caecfc0fe00068541bc709c7c05684 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Mon, 16 Mar 2026 23:14:14 +0530 Subject: [PATCH 05/24] [IMP] Estate:Errors Fixed & Completed Chapter-6 Added list view with key property fields for display. Added form view with grouped fields and description tab. Added search view with title and postcode search fields. Added Available filter using date_availability domain. Added Group By postcode option in search view. --- estate/models/estate_property.py | 22 ++++--- estate/views/estate_menus.xml | 4 +- estate/views/estate_property_views.xml | 83 ++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 14 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 6e87ec1cb91..0fb1e631e83 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -9,9 +9,9 @@ class EstateProperty(models.Model): expected_price = fields.Float(required=True) description = fields.Text() postcode = fields.Char() - date_availability = fields.Date(copy=False) - selling_price = fields.Float(readonly=True,copy=False) - bedrooms = fields.Integer(default= 2) + date_availability = fields.Date(copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) + selling_price = fields.Float(readonly=True, copy=False) + bedrooms = fields.Integer(default=2) living_area = fields.Integer() active = fields.Boolean(default=True) facades = fields.Integer() @@ -19,22 +19,20 @@ class EstateProperty(models.Model): garden = fields.Boolean() garden_area = fields.Integer() garden_orientation = fields.Selection( - string='Orientation', - selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], + string="Orientation", + selection=[('north', "North"), ('south', "South"), ('east', "East"), ('west', "West")], help="The direction the garden faces." - ) state = fields.Selection( selection=[ - ('new', 'New'), - ('offer_received', 'Offer Received'), - ('offer_accepted', 'Offer Accepted'), - ('sold', 'Sold'), - ('canceled', 'Cancelled'), + ('new', "New"), + ('offer_received', "Offer Received"), + ('offer_accepted', "Offer Accepted"), + ('sold', "Sold"), + ('canceled', "Cancelled"), ], string="Status", required=True, copy=False, default='new', ) - \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 1c6d53ac7bd..5d240e46a1a 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -2,6 +2,6 @@ + action="estate_property_action" + parent="estate_first_level_menu"/> diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 4d544c18597..1e3ef74349f 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -4,4 +4,87 @@ estate.property list,form + + + estate.property.list + estate.property + + + + + + + + + + + + + + + estate.property.form + estate.property + +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + estate.property.search + estate.property + + + + + + + + + + + + + + + From 2db9029671811520bc77dc232bc1268630e0a972 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Wed, 18 Mar 2026 18:44:31 +0530 Subject: [PATCH 06/24] [IMP] Estate: Completed Chapter-7 Added Many2one fields for property type, buyer and salesman to main model. Added Many2many tag_ids field and created estate.property.tag model. Added One2many offer_ids field and created estate.property.offer model. Added corresponding menus,actions,views and access rights for all new models. --- estate/__manifest__.py | 3 + estate/models/__init__.py | 3 + estate/models/estate_property.py | 26 +++- estate/models/estate_property_offer.py | 25 ++++ estate/models/estate_property_tag.py | 8 ++ estate/models/estate_property_type.py | 8 ++ estate/security/ir.model.access.csv | 5 +- estate/views/estate_menus.xml | 16 ++- estate/views/estate_property_offer_views.xml | 13 ++ estate/views/estate_property_tag_views.xml | 33 +++++ estate/views/estate_property_type_views.xml | 33 +++++ estate/views/estate_property_views.xml | 133 +++++++++++-------- 12 files changed, 242 insertions(+), 64 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 b6699025785..708f7a1e773 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -5,6 +5,9 @@ 'depends': ['base'], 'data': [ 'security/ir.model.access.csv', + 'views/estate_property_type_views.xml', + 'views/estate_property_tag_views.xml', + 'views/estate_property_offer_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', ], diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 5e1963c9d2f..2f1821a39c1 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -1 +1,4 @@ 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 0fb1e631e83..e657a2b1d1b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -2,7 +2,7 @@ class EstateProperty(models.Model): - _name = "estate.property" + _name = 'estate.property' _description = "Real Estate Property" name = fields.Char(required=True) @@ -36,3 +36,27 @@ class EstateProperty(models.Model): copy=False, default='new', ) + property_type_id = fields.Many2one( + "estate.property.type", + string="Property Type", +) + + buyer_id = fields.Many2one( + "res.partner", + string="Buyer", + copy=False, +) + salesman_id = fields.Many2one( + "res.users", + string="Salesman", + default=lambda self: self.env.user, +) + tag_ids = fields.Many2many( + "estate.property.tag", + string="Tags", +) + offer_ids = fields.One2many( + "estate.property.offer", + "property_id", + string="Offers", +) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py new file mode 100644 index 00000000000..9765b373d00 --- /dev/null +++ b/estate/models/estate_property_offer.py @@ -0,0 +1,25 @@ +from odoo import fields, models + + +class EstatePropertyOffer(models.Model): + _name = "estate.property.offer" + _description = "Real Estate Property Offer" + + price = fields.Float() + status = fields.Selection( + 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 + ) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py new file mode 100644 index 00000000000..198c1037c41 --- /dev/null +++ b/estate/models/estate_property_tag.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyTag(models.Model): + _name = "estate.property.tag" + _description = "Real Estate Property Tag" + + name = fields.Char(required=True) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py new file mode 100644 index 00000000000..ced40ef01cc --- /dev/null +++ b/estate/models/estate_property_type.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class EstatePropertyType(models.Model): + _name = "estate.property.type" + _description = "Real Estate Property Type" + + name = fields.Char(required=True) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 0e11f47e58d..05bd9eefba4 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,access_estate_property,model_estate_property,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property,access_estate_property,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 \ No newline at end of file diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 5d240e46a1a..becc72c64fb 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,7 +1,17 @@ - - + + + + + + + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml new file mode 100644 index 00000000000..92ab9007f2b --- /dev/null +++ b/estate/views/estate_property_offer_views.xml @@ -0,0 +1,13 @@ + + + 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 00000000000..056fbe8b61e --- /dev/null +++ b/estate/views/estate_property_tag_views.xml @@ -0,0 +1,33 @@ + + + Property Tags + estate.property.tag + list,form + + + + estate.property.tag.list + estate.property.tag + + + + + + + + + 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 00000000000..b0713eaaa77 --- /dev/null +++ b/estate/views/estate_property_type_views.xml @@ -0,0 +1,33 @@ + + + Property Types + estate.property.type + list,form + + + + estate.property.type.list + estate.property.type + + + + + + + + + estate.property.type.form + estate.property.type + +
+ +
+

+ +

+
+
+
+
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 1e3ef74349f..88768d4a3f3 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -5,8 +5,7 @@ list,form - + estate.property.list estate.property @@ -22,69 +21,85 @@ - - estate.property.form - estate.property - -
- -
-

- -

-
- - - - - + + estate.property.form + estate.property + + + +
+

+ +

+
+ - - - -
- - - - - - - - - - + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
-
+
-
+
- - estate.property.search - estate.property - - - - - - - - - - + + estate.property.search + estate.property + + + + + + + + - + string="Available" + name="available" + domain="[('date_availability','<=', context_today())]"/> + + + - - + + From 1f35744e35ab842ced55db8a6eaecb9e5b2cd420 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Mon, 30 Mar 2026 22:41:24 +0530 Subject: [PATCH 07/24] [IMP] estate: Partially Completed Chapter-8 --- estate/models/estate_property.py | 28 ++++++++++++++++++- estate/models/estate_property_offer.py | 37 +++++++++++++++++++++++--- estate/views/estate_menus.xml | 22 +++++++-------- estate/views/estate_property_views.xml | 19 ++++++++++++- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index e657a2b1d1b..ce886472de6 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstateProperty(models.Model): @@ -60,3 +60,29 @@ class EstateProperty(models.Model): "property_id", string="Offers", ) + total_area = fields.Integer( + compute="_compute_total_area", + string="Total Area (sqm)", +) + + @api.depends("living_area", "garden_area") + def _compute_total_area(self): + for record in self: + record.total_area = ( + record.living_area + + record.garden_area +) + best_price = fields.Float( + compute="_compute_best_price", + string="Best Offer", +) + + @api.depends("offer_ids.price") + def _compute_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 diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 9765b373d00..9f8d6b30763 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,4 +1,4 @@ -from odoo import fields, models +from odoo import api, fields, models class EstatePropertyOffer(models.Model): @@ -8,8 +8,8 @@ class EstatePropertyOffer(models.Model): price = fields.Float() status = fields.Selection( selection=[ - ('accepted', 'Accepted'), - ('refused', 'Refused'), + ('accepted', "Accepted"), + ('refused', "Refused"), ], copy=False ) @@ -23,3 +23,34 @@ class EstatePropertyOffer(models.Model): string="Property", required=True ) + validity = fields.Integer( + string="Validity (days)", + default=7, + ) + date_deadline = fields.Date( + string="Deadline", + compute="_compute_date_deadline", + inverse="_set_date_deadline", + ) + + @api.depends("validity", "create_date") + def _compute_date_deadline(self): + for record in self: + if record.create_date: + record.date_deadline = fields.Date.add( + record.create_date, + days=record.validity, + ) + else: + record.date_deadline = fields.Date.add( + fields.Date.today(), + days=record.validity, + ) + + def _set_date_deadline(self): + for record in self: + if record.create_date and record.date_deadline: + record.validity = ( + record.date_deadline - + record.create_date.date() + ).days diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index becc72c64fb..63393aa0d61 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -1,17 +1,17 @@ - - + + - + - + - + - + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 88768d4a3f3..3b805c98c1c 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -17,6 +17,12 @@ + +

diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 18e217d3477..a40367673df 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -3,20 +3,25 @@ Properties estate.property list,form + {'search_default_available': 1} estate.property.list estate.property - + - + +

- + - + @@ -66,9 +71,9 @@ - + - + @@ -79,7 +84,8 @@ - +
@@ -98,10 +104,18 @@ + + + Date: Mon, 13 Apr 2026 19:36:56 +0530 Subject: [PATCH 13/24] [IMP] estate: Completed Ch-12 - Add ondelete constraint to prevent deletion of non-new/cancelled properties - Override create() in offers to validate price and set offer_received state - Extend res.users model with property_ids One2many field via model inheritance - Add Real Estate Properties tab to user form view via view inheritance --- estate/__manifest__.py | 1 + estate/models/__init__.py | 1 + estate/models/estate_property.py | 53 ++++++++++++++++++++ estate/models/estate_property_offer.py | 48 ++++++++++++++++++ estate/models/res_users.py | 12 +++++ estate/views/estate_menus.xml | 3 ++ estate/views/estate_property_offer_views.xml | 35 +++++++++++-- estate/views/estate_property_type_views.xml | 2 +- estate/views/estate_property_views.xml | 4 ++ estate/views/res_users_views.xml | 30 +++++++++++ 10 files changed, 184 insertions(+), 5 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 02840fad2e4..527f0da580c 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -10,6 +10,7 @@ 'views/estate_property_tag_views.xml', 'views/estate_property_views.xml', 'views/estate_menus.xml', + 'views/res_users_views.xml', ], 'installable': True, 'application': True, diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 2f1821a39c1..9a2189b6382 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -2,3 +2,4 @@ from . import estate_property_type from . import estate_property_tag from . import estate_property_offer +from . import res_users diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 61cb130b9eb..7dfa9cd7ffc 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -58,6 +58,8 @@ class EstateProperty(models.Model): tag_ids = fields.Many2many( "estate.property.tag", string="Tags", + compute="_compute_tags", + store=True, ) offer_ids = fields.One2many( "estate.property.offer", @@ -69,6 +71,10 @@ class EstateProperty(models.Model): compute="_compute_total_area", string="Total Area (sqm)", ) + has_suspicious_offers = fields.Boolean( + string="Has Suspicious Offers", + compute="_compute_has_suspicious_offers" +) @api.depends("living_area", "garden_area") def _compute_total_area(self): @@ -145,3 +151,50 @@ def _check_seling_price(self): ) < 0: raise ValidationError("Selling price cannot be lower than 90%" "of expected price!") + + def _search_tag(self, tag_name): + return self.env['estate.property.tag'].search( + [('name', '=', tag_name)], limit=1 + ) + + def _create_tag(self, tag_name): + return self.env['estate.property.tag'].create( + {'name': tag_name} + ) + + def _get_or_create_tag(self, tag_name): + return self._search_tag(tag_name) or self._create_tag(tag_name) + + @api.depends('expected_price', 'offer_ids', 'sold_date', 'date_availability', 'state') + def _compute_tags(self): + for record in self: + tag_records = self.env['estate.property.tag'] + + if record.expected_price > 200000: + tag_records |= self._get_or_create_tag('High Value') + + if (record.state == 'sold' + and record.sold_date + and record.date_availability + and (record.sold_date - record.date_availability).days <= 10): + tag_records |= self._get_or_create_tag('Quick Sale') + + if len(record.offer_ids) < 2: + tag_records |= self._get_or_create_tag('Low Interest') + + record.tag_ids = tag_records + + @api.ondelete(at_uninstall=False) + def _unlink_if_new_or_canceled(self): + for record in self: + if record.state not in ('new', 'canceled'): + raise UserError( + "Only new and cancelled properties can be deleted!" + ) + + @api.depends("offer_ids.is_suspicious") + def _compute_has_suspicious_offers(self): + for record in self: + record.has_suspicious_offers = any( + offer.is_suspicious for offer in record.offer_ids + ) diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index a980e38f73b..79403ffa9b7 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -1,5 +1,6 @@ from odoo import api, fields, models from odoo.exceptions import UserError +from datetime import timedelta class EstatePropertyOffer(models.Model): @@ -40,6 +41,14 @@ class EstatePropertyOffer(models.Model): compute="_compute_date_deadline", inverse="_set_date_deadline", ) + is_suspicious = fields.Boolean( + string="Suspicious", + default=False, + readonly=True, + copy=False, + compute="_compute_is_suspicious", + store=True, +) @api.depends("validity", "create_date") def _compute_date_deadline(self): @@ -70,6 +79,10 @@ def action_accept(self): self.property_id.selling_price = self.price self.property_id.buyer_id = self.partner_id self.property_id.state = 'offer_accepted' + + for offer in self.property_id.offer_ids: + if offer.id != self.id: + offer.status = 'refused' return True def action_refuse(self): @@ -81,3 +94,38 @@ def action_refuse(self): 'CHECK(price > 0)', 'Offer price must be strictly positive!', ) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + property_id = self.env['estate.property'].browse( + vals.get('property_id', False) + ) + if property_id.offer_ids: + max_offer = max(property_id.offer_ids.mapped('price')) + if vals.get('price', 0) < max_offer: + raise UserError( + "You cannot create an offer lower " + "than an existing offer of %.2f" % max_offer + ) + + property_id.state = 'offer_received' + + return super().create(vals_list) + + @api.depends("partner_id", "create_date") + def _compute_is_suspicious(self): + for record in self: + if not record.create_date: + record.is_suspicious = False + continue + + start = (record.create_date - timedelta(minutes=5)) + end = (record.create_date + timedelta(minutes=5)) + recent_offers = self.search([ + ('partner_id', '=', record.partner_id.id), + ('create_date', '>=', start), + ('create_date', '<=', end), + ]) + + record.is_suspicious = len(recent_offers) >= 3 diff --git a/estate/models/res_users.py b/estate/models/res_users.py new file mode 100644 index 00000000000..f88e1bb18cc --- /dev/null +++ b/estate/models/res_users.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResUsers(models.Model): + _inherit = "res.users" + + property_ids = fields.One2many( + "estate.property", + "salesman_id", + string="Assigned Properties", + domain=[('state', 'in', ['new', 'offer_received'])] + ) diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 63393aa0d61..2d0f697fb14 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -14,4 +14,7 @@ + + diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml index 3549a5bbc7b..468bb108830 100644 --- a/estate/views/estate_property_offer_views.xml +++ b/estate/views/estate_property_offer_views.xml @@ -1,10 +1,8 @@ - Property Offers estate.property.offer list,form - [('property_type_id', '=', active_id)] @@ -12,8 +10,7 @@ estate.property.offer + decoration-success="status=='accepted'"> @@ -21,6 +18,7 @@ + +

@@ -89,7 +97,32 @@ + readonly="state in ('offer_accepted','sold','canceled')"> + + + + + + + + + -

+ +

@@ -101,28 +107,27 @@ - - - - - - - - + +

+ + diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js index 4ac769b0aa5..dbb268c4d18 100644 --- a/awesome_owl/static/src/playground.js +++ b/awesome_owl/static/src/playground.js @@ -1,5 +1,20 @@ -import { Component } from "@odoo/owl"; +/** @odoo-module **/ +import { Component, useState, markup } from "@odoo/owl"; +import { Counter } from "./counter/counter"; +import { Card } from "./card/card"; +import { TodoList } from "./todo_list/todo_list"; export class Playground extends Component { - static template = "awesome_owl.playground"; + static template = "awesome_owl.Playground"; + static components = { Counter, Card, TodoList }; + + setup() { + this.state = useState({ sum: 0 }); + this.safeHtml = markup("
some content
"); + this.unsafeHtml = "
some content
"; + } + + incrementSum() { + this.state.sum++; + } } diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml index 4fb905d59f9..b06c0be9cb3 100644 --- a/awesome_owl/static/src/playground.xml +++ b/awesome_owl/static/src/playground.xml @@ -1,10 +1,32 @@ - + + + +
+ +

Hello Counter

+ + + + +

+ The sum is: +

+ +
+ + This is simple text inside card + + + + +
+ +
+ +
- -
- hello world
-
+ diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js new file mode 100644 index 00000000000..efc3053a547 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.js @@ -0,0 +1,19 @@ +/** @odoo-module **/ +import { Component } from "@odoo/owl"; + +export class TodoItem extends Component { + static template = "awesome_owl.TodoItem"; + + static props = { + todo: { + type: Object, + shape: { + id: Number, + description: String, + isCompleted: Boolean, + }, + }, + toggleState: { type: Function }, + removeTodo: { type: Function }, + }; +} diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml new file mode 100644 index 00000000000..14126c9e69d --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_item.xml @@ -0,0 +1,26 @@ + + + +
+ + + + + . + + + + + +
+
+
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js new file mode 100644 index 00000000000..04dfb0f57c7 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.js @@ -0,0 +1,45 @@ +/** @odoo-module **/ +import { Component, useState, useRef, onMounted } from "@odoo/owl"; +import { TodoItem } from "./todo_item"; + +export class TodoList extends Component { + static template = "awesome_owl.TodoList"; + static components = { TodoItem }; + + setup() { + this.todos = useState([]); + this.nextId = 1; + this.inputRef = useRef("todoInput"); + + onMounted(() => { + this.inputRef.el.focus(); + }); + } + + addTodo(ev) { + if (ev.keyCode === 13) { + const description = ev.target.value.trim(); + if (!description) return; + this.todos.push({ + id: this.nextId++, + description: description, + isCompleted: false, + }); + ev.target.value = ""; + } + } + + toggleState(todoId) { + const todo = this.todos.find(t => t.id === todoId); + if (todo) { + todo.isCompleted = !todo.isCompleted; + } + } + + removeTodo(todoId) { + const index = this.todos.findIndex(t => t.id === todoId); + if (index >= 0) { + this.todos.splice(index, 1); + } + } +} diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml new file mode 100644 index 00000000000..7b19de79303 --- /dev/null +++ b/awesome_owl/static/src/todo_list/todo_list.xml @@ -0,0 +1,26 @@ + + + +
+ +

+ Todo List +

+ + + + + + + +
+
+
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index a3aa2bf275d..4bca52a575e 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -44,7 +44,6 @@ class EstateProperty(models.Model): "estate.property.type", string="Property Type", ) - buyer_id = fields.Many2one( "res.partner", string="Buyer", @@ -112,9 +111,7 @@ def _compute_visit_count(self): @api.depends("living_area", "garden_area") def _compute_total_area(self): for record in self: - record.total_area = ( - record.living_area + - record.garden_area + record.total_area = (record.living_area + record.garden_area ) @api.depends("offer_ids.price") @@ -186,9 +183,11 @@ def action_sold(self): raise UserError("Cancelled property cannot be sold!") for rec in record.issue_ids: if rec.staty != 'resolved' and rec.priority == 'high': - raise UserError("Cannot sell the property pls solve the issues") + raise UserError( + "Cannot sell the property, please solve the issues" + ) record.state = 'sold' - record.sold_date = fields.Datetime.now() + record.sold_date = fields.Date.today() return True def action_cancel(self): diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index 546554e3765..a055585ecb4 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -4,4 +4,4 @@ access_estate_property_type,access_estate_property_type,model_estate_property_ty 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 access_estate_property_visit,estate.property.visit,model_estate_property_visit,base.group_user,1,1,1,1 -access_estate_property_issue,estate.property.issue,model_estate_property_issue,base.group_user,1,1,1,1 \ No newline at end of file +access_estate_property_issue,estate.property.issue,model_estate_property_issue,base.group_user,1,1,1,1 diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index f4c71b1ff9e..aa87e68e43d 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -26,7 +26,8 @@ name="unlink" string="Delete" type="object" - icon="fa-trash"/> + icon="fa-trash" + /> From d290f4c29d326b37ba3b0472c56b81f8511855df Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Wed, 6 May 2026 14:16:09 +0530 Subject: [PATCH 20/24] [IMP] estate:add chatter in form view and email template on sold - Add chatter to estate.property form view - Inherit mail.thread and mail.activity.mixin in estate.property - Add mail template for property sold - Auto-fill buyer and salesman in partner To field - action_sold opens mail compose wizard with template loaded --- estate/__manifest__.py | 3 ++- estate/data/mail_template_data.xml | 27 ++++++++++++++++++++++++++ estate/models/estate_property.py | 22 +++++++++++++++++++-- estate/views/estate_property_views.xml | 1 + 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 estate/data/mail_template_data.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index 07805908998..b2b12f09676 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,7 +2,7 @@ 'name': 'Real Estate', 'author': 'soham', 'license': 'LGPL-3', - 'depends': ['base', 'calendar'], + 'depends': ['base', 'calendar', 'mail'], 'data': [ 'security/ir.model.access.csv', 'views/estate_property_visit_views.xml', @@ -13,6 +13,7 @@ 'views/estate_property_views.xml', 'views/res_users_views.xml', 'views/estate_menus.xml', + 'data/mail_template_data.xml', ], 'installable': True, 'application': True, diff --git a/estate/data/mail_template_data.xml b/estate/data/mail_template_data.xml new file mode 100644 index 00000000000..dac0617beb4 --- /dev/null +++ b/estate/data/mail_template_data.xml @@ -0,0 +1,27 @@ + + + + + Estate: Property Sold + + {{object.name}} Sold + + {{ object.salesman_id.email_formatted }} + + +
+

Dear ,

+

Property + + has been sold.

+

Seller:

+

Selling Price:

+

Description:

+

Thank you!

+
+
+
+
+
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 4bca52a575e..25cd16b8e3b 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -5,13 +5,14 @@ class EstateProperty(models.Model): _name = 'estate.property' + _inherit = ['mail.thread', 'mail.activity.mixin'] _description = "Real Estate Property" _order = "id desc" name = fields.Char(required=True) expected_price = fields.Float(required=True) description = fields.Text() - postcode = fields.Char() + postcode = fields.Char(tracking=1) date_availability = fields.Date(copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) selling_price = fields.Float(readonly=True, copy=False) bedrooms = fields.Integer(default=2) @@ -188,7 +189,24 @@ def action_sold(self): ) record.state = 'sold' record.sold_date = fields.Date.today() - return True + + ctx = { + 'default_model': 'estate.property', + 'default_res_ids': self.ids, + 'default_template_id': self.env.ref('estate.email_template_estate_property_sold').id, + 'default_partner_ids': [self.buyer_id.id, self.salesman_id.partner_id.id], + } + + return { + 'name': 'Sold', + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_model': 'mail.compose.message', + 'views': [(False, 'form')], + 'view_id': False, + 'target': 'new', + 'context': ctx, + } def action_cancel(self): for record in self: diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index aa87e68e43d..93093a6eacb 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -132,6 +132,7 @@ + From 6b3a11d4d8e2d5d8aeea662d761a2ba751533c83 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Thu, 7 May 2026 10:54:34 +0530 Subject: [PATCH 21/24] [ADD] estate_crm:auto generate crm lead on offer and update stage - Create estate_crm module - Create CRM lead on offer creation with property name, buyer and price - Mark lead as won when offer is accepted - Mark lead as lost when offer is refused - Update action_accept to call action_refuse on other offers so refused leads automatically move to lost stage --- estate_crm/__init__.py | 1 + estate_crm/__manifest__.py | 14 ++++++++++ estate_crm/models/__init__.py | 1 + estate_crm/models/estate_property.py | 41 ++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+) create mode 100644 estate_crm/__init__.py create mode 100644 estate_crm/__manifest__.py create mode 100644 estate_crm/models/__init__.py create mode 100644 estate_crm/models/estate_property.py diff --git a/estate_crm/__init__.py b/estate_crm/__init__.py new file mode 100644 index 00000000000..0650744f6bc --- /dev/null +++ b/estate_crm/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/estate_crm/__manifest__.py b/estate_crm/__manifest__.py new file mode 100644 index 00000000000..75ac55d6fb8 --- /dev/null +++ b/estate_crm/__manifest__.py @@ -0,0 +1,14 @@ +{ + 'name': 'Real Estate Lead', + 'version': '1.0', + 'author': 'soham', + 'license': 'LGPL-3', + 'category': 'Real Estate', + 'depends': [ + 'estate', + 'crm', + ], + 'data': [], + 'installable': True, + 'application': True, +} diff --git a/estate_crm/models/__init__.py b/estate_crm/models/__init__.py new file mode 100644 index 00000000000..5e1963c9d2f --- /dev/null +++ b/estate_crm/models/__init__.py @@ -0,0 +1 @@ +from . import estate_property diff --git a/estate_crm/models/estate_property.py b/estate_crm/models/estate_property.py new file mode 100644 index 00000000000..ab95af50c87 --- /dev/null +++ b/estate_crm/models/estate_property.py @@ -0,0 +1,41 @@ +from odoo import api, fields, models + + +class EstatePropertyOffer(models.Model): + _inherit = "estate.property.offer" + + lead_id = fields.Many2one( + "crm.lead", + string="CRM Lead", + readonly=True, + copy=False + ) + + @api.model_create_multi + def create(self, vals_list): + records = super().create(vals_list) + for record in records: + lead = self.env["crm.lead"].create({ + "name": record.property_id.name, + "partner_id": record.partner_id.id, + "expected_revenue": record.price, + }) + record.lead_id = lead.id + return records + + def action_accept(self): + result = super().action_accept() + for offer in self: + if offer.lead_id: + offer.lead_id.action_set_won() + for offer in self.property_id.offer_ids: + if offer.id != self.id: + offer.action_refuse() + return result + + def action_refuse(self): + result = super().action_refuse() + for offer in self: + if offer.lead_id: + offer.lead_id.action_set_lost() + return result From 8879c0ecb0d414ddca1d5a7e59fd776637261280 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Sat, 9 May 2026 17:03:44 +0530 Subject: [PATCH 22/24] [ADD] awesome_dashboard:build a dynamic dashboard - Add Layout component with control panel buttons - Add Customers and Leads navigation buttons using action service - Add statistics service with reactive data and auto-refresh every 10 mins - Call /awesome_dashboard/statistics rpc to fetch server data - Add NumberCard component to display stat values - Add PieChartCard component with lazy loaded Chart.js - Add DashboardItem wrapper component with size support - Add dashboard_items registry with all stat cards - Add dashboard_registry using awesome_dashboard.items category - Add ConfigurationDialog with CheckBox to show/hide cards - Persist hidden cards in localStorage via browser service - Add lazy loading via LazyComponent and lazy_components registry - Add statistics service caching with reactive object --- awesome_dashboard/__manifest__.py | 7 +- awesome_dashboard/static/src/dashboard.js | 8 -- awesome_dashboard/static/src/dashboard.xml | 8 -- .../static/src/dashboard/dashboard.js | 102 ++++++++++++++++++ .../static/src/dashboard/dashboard.scss | 10 ++ .../static/src/dashboard/dashboard.xml | 58 ++++++++++ .../dashboard_item/dashboard_item.js | 9 ++ .../dashboard_item/dashboard_item.xml | 11 ++ .../static/src/dashboard/dashboard_items.js | 71 ++++++++++++ .../src/dashboard/dashboard_registry.js | 3 + .../src/dashboard/number_card/number_card.js | 9 ++ .../src/dashboard/number_card/number_card.xml | 9 ++ .../src/dashboard/pie_chart/pie_chart.js | 34 ++++++ .../src/dashboard/pie_chart/pie_chart.xml | 7 ++ .../pie_chart_card/pie_chart_card.js | 11 ++ .../pie_chart_card/pie_chart_card.xml | 11 ++ .../src/dashboard/statistics_service.js | 26 +++++ .../static/src/dashboard_action.js | 10 ++ .../static/src/dashboard_action.xml | 9 ++ 19 files changed, 396 insertions(+), 17 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/dashboard_item/dashboard_item.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js create mode 100644 awesome_dashboard/static/src/dashboard/dashboard_registry.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.js create mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js create mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml create mode 100644 awesome_dashboard/static/src/dashboard/statistics_service.js create mode 100644 awesome_dashboard/static/src/dashboard_action.js create mode 100644 awesome_dashboard/static/src/dashboard_action.xml diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py index a1cd72893d7..58817d5fbd4 100644 --- a/awesome_dashboard/__manifest__.py +++ b/awesome_dashboard/__manifest__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- { 'name': "Awesome Dashboard", @@ -21,10 +20,16 @@ 'data': [ 'views/views.xml', ], + 'assets': { 'web.assets_backend': [ 'awesome_dashboard/static/src/**/*', ], + + 'awesome_dashboard.dashboard': [ + 'awesome_dashboard/static/src/dashboard/**/*' + ], }, + 'license': 'AGPL-3' } diff --git a/awesome_dashboard/static/src/dashboard.js b/awesome_dashboard/static/src/dashboard.js deleted file mode 100644 index c4fb245621b..00000000000 --- a/awesome_dashboard/static/src/dashboard.js +++ /dev/null @@ -1,8 +0,0 @@ -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 1a2ac9a2fed..00000000000 --- 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 00000000000..2c988a966f1 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.js @@ -0,0 +1,102 @@ +/** @odoo-module **/ +import { Component, useState } from "@odoo/owl"; +import { registry } from "@web/core/registry"; +import { Layout } from "@web/search/layout"; +import { useService } from "@web/core/utils/hooks"; +import { DashboardItem } from "./dashboard_item/dashboard_item"; +import { Dialog } from "@web/core/dialog/dialog"; +import { CheckBox } from "@web/core/checkbox/checkbox"; +import { browser } from "@web/core/browser/browser"; +import "./statistics_service"; +import "./dashboard_items"; + +class AwesomeDashboard extends Component { + static template = "awesome_dashboard.AwesomeDashboard"; + static components = { Layout, DashboardItem }; + + setup() { + this.action = useService("action"); + this.dialog = useService("dialog"); + this.display = { controlPanel: {} }; + this.stats = useService("awesome_dashboard.statistics"); + + this.state = useState({ + disabledItems: JSON.parse( + browser.localStorage.getItem("disabledDashboardItems") || "[]" + ) + }); + + this.allItems = registry + .category("awesome_dashboard.items") + .getAll(); + } + + get items() { + return this.allItems.filter( + item => !this.state.disabledItems.includes(item.id) + ); + } + + openConfiguration() { + this.dialog.add(ConfigurationDialog, { + items: this.allItems, + disabledItems: this.state.disabledItems, + onUpdate: this.updateConfiguration.bind(this), + }); + } + + updateConfiguration(newDisabledItems) { + this.state.disabledItems = newDisabledItems; + browser.localStorage.setItem( + "disabledDashboardItems", + JSON.stringify(newDisabledItems) + ); + } + + openCustomers() { + this.action.doAction("base.action_partner_form"); + } + + openLeads() { + this.action.doAction({ + type: "ir.actions.act_window", + name: "Leads", + res_model: "crm.lead", + views: [[false, "list"], [false, "form"]], + }); + } +} + +class ConfigurationDialog extends Component { + static template = "awesome_dashboard.ConfigurationDialog"; + static components = { Dialog, CheckBox }; + static props = ["close", "items", "disabledItems", "onUpdate"]; + + setup() { + const disabled = Array.isArray(this.props.disabledItems) + ? this.props.disabledItems + : []; + + this.items = useState( + this.props.items.map(item => ({ + ...item, + enabled: !disabled.includes(item.id), + })) + ); + } + + onChange(checked, item) { + item.enabled = checked; + const disabled = this.items + .filter(i => !i.enabled) + .map(i => i.id); + this.props.onUpdate(disabled); + } + + done() { + this.props.close(); + } +} +registry + .category("lazy_components") + .add("awesome_dashboard.AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss new file mode 100644 index 00000000000..ffd265b1982 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.scss @@ -0,0 +1,10 @@ +.o_dashboard { + background-color: rgb(16, 18, 20); +} + +@media (max-width: 768px) { + + .o_dashboard_item { + width: 100% !important; + } +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml new file mode 100644 index 00000000000..4df87c37ab1 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js new file mode 100644 index 00000000000..818c69c580f --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class DashboardItem extends Component { + static template = "awesome_dashboard.DashboardItem"; + + static slots = { + default: {}, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml new file mode 100644 index 00000000000..82e3f8055d3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml @@ -0,0 +1,11 @@ + + + + + +
+ +
+ +
+
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js new file mode 100644 index 00000000000..695b557c2f8 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_items.js @@ -0,0 +1,71 @@ +/** @odoo-module **/ +import { dashboardItemRegistry } from "./dashboard_registry"; +import { NumberCard } from "./number_card/number_card"; +import { PieChartCard } from "./pie_chart_card/pie_chart_card"; +import { _t } from "@web/core/l10n/translation"; + +dashboardItemRegistry.add("new_orders", { + id: "new_orders", + description: _t("New orders this month"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("Number of new orders"), + value: data?.nb_new_orders || 0, + }), +}); + +dashboardItemRegistry.add("total_amount", { + id: "total_amount", + description: _t("Total amount orders this month"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("Total amount"), + value: data?.total_amount || 0, + }), +}); + +dashboardItemRegistry.add("avg_quantity", { + id: "avg_quantity", + description: _t("Average amount of t-shirt"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("Average quantity"), + value: data?.average_quantity || 0, + }), +}); + +dashboardItemRegistry.add("average_time", { + id: "average_time", + description: _t("Average time new to sent/cancelled"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("Average time"), + value: data?.average_time || 0, + }), +}); + +dashboardItemRegistry.add("nb_cancelled_orders", { + id: "nb_cancelled_orders", + description: _t("Cancelled orders this month"), + Component: NumberCard, + size: 1, + props: (data) => ({ + title: _t("Number of cancelled orders"), + value: data?.nb_cancelled_orders || 0, + }), +}); + +dashboardItemRegistry.add("pie_chart", { + id: "pie_chart", + description: _t("Shirt orders by size"), + Component: PieChartCard, + size: 2, + props: (data) => ({ + title: _t("Orders by size"), + data: data?.orders_by_size || {}, + }), +}); diff --git a/awesome_dashboard/static/src/dashboard/dashboard_registry.js b/awesome_dashboard/static/src/dashboard/dashboard_registry.js new file mode 100644 index 00000000000..e3fe596f7ad --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/dashboard_registry.js @@ -0,0 +1,3 @@ +import { registry } from "@web/core/registry"; + +export const dashboardItemRegistry = registry.category("awesome_dashboard.items"); diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js new file mode 100644 index 00000000000..95cc39c8b33 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.js @@ -0,0 +1,9 @@ +import { Component } from "@odoo/owl"; + +export class NumberCard extends Component { + static template = "awesome_dashboard.NumberCard"; + static props = { + title: String, + value: { type: Number, optional: true }, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml new file mode 100644 index 00000000000..1da288b98a9 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml @@ -0,0 +1,9 @@ + + + +
+
+

+
+
+
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js new file mode 100644 index 00000000000..4fb8c3bacaa --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js @@ -0,0 +1,34 @@ +import { Component, onWillStart, onMounted, useRef } from "@odoo/owl"; +import { loadJS } from "@web/core/assets"; + +export class PieChart extends Component { + static template = "awesome_dashboard.PieChart"; + + static props = { + data: Object, + }; + + setup() { + this.canvasRef = useRef("chart"); + onWillStart(async () => { + await loadJS("/web/static/lib/Chart/Chart.js"); + }); + onMounted(() => { + this.renderChart(); + }); + } + + renderChart() { + const ctx = this.canvasRef.el.getContext("2d"); + + new Chart(ctx, { + type: "pie", + data: { + labels: Object.keys(this.props.data), + datasets: [{ + data: Object.values(this.props.data), + }], + }, + }); + } +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml new file mode 100644 index 00000000000..6d356ee58fb --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js new file mode 100644 index 00000000000..23aa93ec2c3 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js @@ -0,0 +1,11 @@ +import { Component } from "@odoo/owl"; +import { PieChart } from "../pie_chart/pie_chart"; + +export class PieChartCard extends Component { + static template = "awesome_dashboard.PieChartCard"; + static components = { PieChart } + static props = { + title: String, + data: Object, + }; +} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml new file mode 100644 index 00000000000..f22aac77842 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml @@ -0,0 +1,11 @@ + + + + +
+
+ +
+
+ +
diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js new file mode 100644 index 00000000000..fe717b5e350 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard/statistics_service.js @@ -0,0 +1,26 @@ +/** @odoo-module **/ +import { registry } from "@web/core/registry"; +import { rpc } from "@web/core/network/rpc"; +import { reactive } from "@odoo/owl"; + +const statisticsService = { + name: "awesome_dashboard.statistics", + + start() { + const stats = reactive({}); + + async function loadStatistics() { + const data = await rpc("/awesome_dashboard/statistics"); + Object.assign(stats, data); + } + + loadStatistics(); + setInterval(loadStatistics, 10 * 60 * 1000); + + return stats; + }, +}; + +registry + .category("services") + .add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js new file mode 100644 index 00000000000..52decad3f86 --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.js @@ -0,0 +1,10 @@ +import { Component } from "@odoo/owl"; +import { LazyComponent } from "@web/core/assets"; +import { registry } from "@web/core/registry"; + +export class DashboardAction extends Component { + static components = { LazyComponent }; + static template = "awesome_dashboard.DashboardLoader"; +} + +registry.category("actions").add("awesome_dashboard.dashboard", DashboardAction); diff --git a/awesome_dashboard/static/src/dashboard_action.xml b/awesome_dashboard/static/src/dashboard_action.xml new file mode 100644 index 00000000000..5beaede125b --- /dev/null +++ b/awesome_dashboard/static/src/dashboard_action.xml @@ -0,0 +1,9 @@ + + + + + + From ca889d2bd25e27af0e571688169ae83e28f101a1 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Thu, 14 May 2026 16:00:08 +0530 Subject: [PATCH 23/24] [IMP] estate: Added review task --- estate/__manifest__.py | 6 ++-- estate/data/ir_cron.xml | 12 +++++++ estate/models/__init__.py | 1 + estate/models/estate_property.py | 24 ++++++++++++- estate/models/estate_property_offer.py | 18 ++++++++++ estate/models/estate_property_wizard.py | 27 +++++++++++++++ estate/security/ir.model.access.csv | 1 + estate/views/estate_menus.xml | 3 ++ estate/views/estate_property_views.xml | 8 +++++ estate/views/estate_property_wizard_views.xml | 34 +++++++++++++++++++ 10 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 estate/data/ir_cron.xml create mode 100644 estate/models/estate_property_wizard.py create mode 100644 estate/views/estate_property_wizard_views.xml diff --git a/estate/__manifest__.py b/estate/__manifest__.py index b2b12f09676..5cdf7aed65f 100644 --- a/estate/__manifest__.py +++ b/estate/__manifest__.py @@ -2,18 +2,20 @@ 'name': 'Real Estate', 'author': 'soham', 'license': 'LGPL-3', - 'depends': ['base', 'calendar', 'mail'], + 'depends': ['base', 'calendar', 'mail', 'sale'], 'data': [ 'security/ir.model.access.csv', + 'data/mail_template_data.xml', + 'data/ir_cron.xml', 'views/estate_property_visit_views.xml', 'views/estate_property_issue_views.xml', 'views/estate_property_offer_views.xml', 'views/estate_property_type_views.xml', 'views/estate_property_tag_views.xml', + 'views/estate_property_wizard_views.xml', 'views/estate_property_views.xml', 'views/res_users_views.xml', 'views/estate_menus.xml', - 'data/mail_template_data.xml', ], 'installable': True, 'application': True, diff --git a/estate/data/ir_cron.xml b/estate/data/ir_cron.xml new file mode 100644 index 00000000000..9d1cf480adc --- /dev/null +++ b/estate/data/ir_cron.xml @@ -0,0 +1,12 @@ + + + + Offer: Expired Offers Refused + + code + model._cron_automatic_refuse() + + 5 + minutes + + diff --git a/estate/models/__init__.py b/estate/models/__init__.py index 15f719c5492..dfacdea237e 100644 --- a/estate/models/__init__.py +++ b/estate/models/__init__.py @@ -5,3 +5,4 @@ from . import res_users from . import estate_property_visit from . import estate_property_issue +from . import estate_property_wizard diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py index 25cd16b8e3b..41b538fe1e7 100644 --- a/estate/models/estate_property.py +++ b/estate/models/estate_property.py @@ -1,4 +1,4 @@ -from odoo import api, fields, models +from odoo import api, Command, fields, models from odoo.exceptions import UserError, ValidationError from odoo.tools.float_utils import float_compare, float_is_zero @@ -217,6 +217,28 @@ def action_cancel(self): record.state = 'canceled' return True + def action_create_quotations(self): + if not self.offer_ids: + raise UserError("No offers found") + + for offer in self.offer_ids: + if offer.quotation_id: + continue + quotation = self.env["sale.order"].create({ + "partner_id": offer.partner_id.id, + "origin": self.name, + "order_line": [ + Command.create({ + "name": self.name, + "product_uom_qty": 1, + "price_unit": offer.price, + }) + ], + }) + + offer.quotation_id = quotation.id + return True + def _search_tag(self, tag_name): return self.env['estate.property.tag'].search( [('name', '=', tag_name)], limit=1 diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py index 64413a413d5..e9de6781318 100644 --- a/estate/models/estate_property_offer.py +++ b/estate/models/estate_property_offer.py @@ -36,6 +36,12 @@ class EstatePropertyOffer(models.Model): string="Validity (days)", default=7, ) + quotation_id = fields.Many2one( + "sale.order", + string="Quotation", + readonly=True, + copy=False + ) date_deadline = fields.Date( string="Deadline", compute="_compute_date_deadline", @@ -129,3 +135,15 @@ def action_refuse(self): for record in self: record.status = 'refused' return True + + def _cron_automatic_refuse(self): + today = fields.Datetime.now() + offers = self.search([ + ('status', "not in", ["accepted", "refused"]), + ]) + for offer in offers: + expiry_date = offer.create_date + timedelta(days=offer.validity) + if expiry_date < today: + offer.write({ + 'status': 'refused' + }) diff --git a/estate/models/estate_property_wizard.py b/estate/models/estate_property_wizard.py new file mode 100644 index 00000000000..42cb0528067 --- /dev/null +++ b/estate/models/estate_property_wizard.py @@ -0,0 +1,27 @@ +from odoo import fields, models + + +class EstatePropertyWizard(models.TransientModel): + _name = "estate.property.wizard" + _description = "Create Offers" + + price = fields.Float() + + partner_id = fields.Many2one( + "res.partner", + string="Buyer", + copy=False, + ) + + def action_create_offers(self): + + properties = self.env["estate.property"].search([ + ("state", "not in", ["sold", "canceled", "offer_accepted"]) + ]) + for property in properties: + self.env["estate.property.offer"].create({ + "price": self.price, + "partner_id": self.partner_id.id, + "property_id": property.id, + }) + return True diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv index a055585ecb4..051e7c3e2c4 100644 --- a/estate/security/ir.model.access.csv +++ b/estate/security/ir.model.access.csv @@ -5,3 +5,4 @@ access_estate_property_tag,access_estate_property_tag,model_estate_property_tag, access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 access_estate_property_visit,estate.property.visit,model_estate_property_visit,base.group_user,1,1,1,1 access_estate_property_issue,estate.property.issue,model_estate_property_issue,base.group_user,1,1,1,1 +access_estate_property_wizard,estate.property.wizard,model_estate_property_wizard,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml index 290958fd750..25fad3f77c3 100644 --- a/estate/views/estate_menus.xml +++ b/estate/views/estate_menus.xml @@ -20,4 +20,7 @@ + + diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml index 93093a6eacb..658f344eee8 100644 --- a/estate/views/estate_property_views.xml +++ b/estate/views/estate_property_views.xml @@ -14,6 +14,13 @@ decoration-bf="state == 'offer_accepted'" decoration-muted="state == 'sold'" delete="True"> +
+
@@ -40,6 +47,7 @@
diff --git a/estate/views/estate_property_wizard_views.xml b/estate/views/estate_property_wizard_views.xml new file mode 100644 index 00000000000..df41da94874 --- /dev/null +++ b/estate/views/estate_property_wizard_views.xml @@ -0,0 +1,34 @@ + + + + + Create Offers + estate.property.wizard + form + new + + + + estate.property.offer.wizard.form + estate.property.wizard + +
+ + + + + +
+
+
+
+
+
From ed00fc9e858188e2e5b34e6dfb3f1152a3149328 Mon Sep 17 00:00:00 2001 From: "Soham Patil (sopat)" Date: Fri, 15 May 2026 10:41:48 +0530 Subject: [PATCH 24/24] [ADD] bom_forecast: clean bom overview forecast view --- awesome_dashboard/__init__.py | 3 - awesome_dashboard/__manifest__.py | 35 --- awesome_dashboard/controllers/__init__.py | 3 - awesome_dashboard/controllers/controllers.py | 36 --- .../static/src/dashboard/dashboard.js | 102 ------- .../static/src/dashboard/dashboard.scss | 10 - .../static/src/dashboard/dashboard.xml | 58 ---- .../dashboard_item/dashboard_item.js | 9 - .../dashboard_item/dashboard_item.xml | 11 - .../static/src/dashboard/dashboard_items.js | 71 ----- .../src/dashboard/dashboard_registry.js | 3 - .../src/dashboard/number_card/number_card.js | 9 - .../src/dashboard/number_card/number_card.xml | 9 - .../src/dashboard/pie_chart/pie_chart.js | 34 --- .../src/dashboard/pie_chart/pie_chart.xml | 7 - .../pie_chart_card/pie_chart_card.js | 11 - .../pie_chart_card/pie_chart_card.xml | 11 - .../src/dashboard/statistics_service.js | 26 -- .../static/src/dashboard_action.js | 10 - .../static/src/dashboard_action.xml | 9 - awesome_dashboard/views/views.xml | 11 - awesome_owl/__init__.py | 3 - awesome_owl/__manifest__.py | 43 --- awesome_owl/controllers/__init__.py | 3 - awesome_owl/controllers/controllers.py | 10 - awesome_owl/static/src/card/card.js | 19 -- awesome_owl/static/src/card/card.xml | 33 --- awesome_owl/static/src/counter/counter.js | 21 -- awesome_owl/static/src/counter/counter.xml | 22 -- awesome_owl/static/src/main.js | 12 - awesome_owl/static/src/playground.js | 20 -- awesome_owl/static/src/playground.xml | 32 --- awesome_owl/static/src/todo_list/todo_item.js | 19 -- .../static/src/todo_list/todo_item.xml | 26 -- awesome_owl/static/src/todo_list/todo_list.js | 45 --- .../static/src/todo_list/todo_list.xml | 26 -- awesome_owl/views/templates.xml | 15 - bom_forecast/__init__.py | 1 + bom_forecast/__manifest__.py | 16 ++ bom_forecast/model/__init__.py | 1 + .../model/report_mrp_bom_structure.py | 15 + bom_forecast/static/src/bom_overview_line.js | 28 ++ bom_forecast/static/src/bom_overview_line.xml | 38 +++ .../static/src/mrp_bom_overview_table.xml | 7 + estate/__init__.py | 1 - estate/__manifest__.py | 22 -- estate/data/ir_cron.xml | 12 - estate/data/mail_template_data.xml | 27 -- estate/models/__init__.py | 8 - estate/models/estate_property.py | 261 ------------------ estate/models/estate_property_issue.py | 105 ------- estate/models/estate_property_offer.py | 149 ---------- estate/models/estate_property_tag.py | 15 - estate/models/estate_property_type.py | 40 --- estate/models/estate_property_visit.py | 36 --- estate/models/estate_property_wizard.py | 27 -- estate/models/res_users.py | 12 - estate/security/ir.model.access.csv | 8 - estate/views/estate_menus.xml | 26 -- estate/views/estate_property_issue_views.xml | 71 ----- estate/views/estate_property_offer_views.xml | 66 ----- estate/views/estate_property_tag_views.xml | 35 --- estate/views/estate_property_type_views.xml | 55 ---- estate/views/estate_property_views.xml | 213 -------------- estate/views/estate_property_visit_views.xml | 40 --- estate/views/estate_property_wizard_views.xml | 34 --- estate/views/res_users_views.xml | 30 -- estate_account/__init__.py | 1 - estate_account/__manifest__.py | 13 - estate_account/models/__init__.py | 1 - estate_account/models/estate_property.py | 27 -- estate_crm/__init__.py | 1 - estate_crm/__manifest__.py | 14 - estate_crm/models/__init__.py | 1 - estate_crm/models/estate_property.py | 41 --- 75 files changed, 106 insertions(+), 2219 deletions(-) delete mode 100644 awesome_dashboard/__init__.py delete mode 100644 awesome_dashboard/__manifest__.py delete mode 100644 awesome_dashboard/controllers/__init__.py delete mode 100644 awesome_dashboard/controllers/controllers.py delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard.js delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard.scss delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard.xml delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard_items.js delete mode 100644 awesome_dashboard/static/src/dashboard/dashboard_registry.js delete mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.js delete mode 100644 awesome_dashboard/static/src/dashboard/number_card/number_card.xml delete mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js delete mode 100644 awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml delete mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js delete mode 100644 awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml delete mode 100644 awesome_dashboard/static/src/dashboard/statistics_service.js delete mode 100644 awesome_dashboard/static/src/dashboard_action.js delete mode 100644 awesome_dashboard/static/src/dashboard_action.xml delete mode 100644 awesome_dashboard/views/views.xml delete mode 100644 awesome_owl/__init__.py delete mode 100644 awesome_owl/__manifest__.py delete mode 100644 awesome_owl/controllers/__init__.py delete mode 100644 awesome_owl/controllers/controllers.py delete mode 100644 awesome_owl/static/src/card/card.js delete mode 100644 awesome_owl/static/src/card/card.xml delete mode 100644 awesome_owl/static/src/counter/counter.js delete mode 100644 awesome_owl/static/src/counter/counter.xml delete mode 100644 awesome_owl/static/src/main.js delete mode 100644 awesome_owl/static/src/playground.js delete mode 100644 awesome_owl/static/src/playground.xml delete mode 100644 awesome_owl/static/src/todo_list/todo_item.js delete mode 100644 awesome_owl/static/src/todo_list/todo_item.xml delete mode 100644 awesome_owl/static/src/todo_list/todo_list.js delete mode 100644 awesome_owl/static/src/todo_list/todo_list.xml delete mode 100644 awesome_owl/views/templates.xml create mode 100644 bom_forecast/__init__.py create mode 100644 bom_forecast/__manifest__.py create mode 100644 bom_forecast/model/__init__.py create mode 100644 bom_forecast/model/report_mrp_bom_structure.py create mode 100644 bom_forecast/static/src/bom_overview_line.js create mode 100644 bom_forecast/static/src/bom_overview_line.xml create mode 100644 bom_forecast/static/src/mrp_bom_overview_table.xml delete mode 100644 estate/__init__.py delete mode 100644 estate/__manifest__.py delete mode 100644 estate/data/ir_cron.xml delete mode 100644 estate/data/mail_template_data.xml delete mode 100644 estate/models/__init__.py delete mode 100644 estate/models/estate_property.py delete mode 100644 estate/models/estate_property_issue.py delete mode 100644 estate/models/estate_property_offer.py delete mode 100644 estate/models/estate_property_tag.py delete mode 100644 estate/models/estate_property_type.py delete mode 100644 estate/models/estate_property_visit.py delete mode 100644 estate/models/estate_property_wizard.py delete mode 100644 estate/models/res_users.py delete mode 100644 estate/security/ir.model.access.csv delete mode 100644 estate/views/estate_menus.xml delete mode 100644 estate/views/estate_property_issue_views.xml delete mode 100644 estate/views/estate_property_offer_views.xml delete mode 100644 estate/views/estate_property_tag_views.xml delete mode 100644 estate/views/estate_property_type_views.xml delete mode 100644 estate/views/estate_property_views.xml delete mode 100644 estate/views/estate_property_visit_views.xml delete mode 100644 estate/views/estate_property_wizard_views.xml delete mode 100644 estate/views/res_users_views.xml delete mode 100644 estate_account/__init__.py delete mode 100644 estate_account/__manifest__.py delete mode 100644 estate_account/models/__init__.py delete mode 100644 estate_account/models/estate_property.py delete mode 100644 estate_crm/__init__.py delete mode 100644 estate_crm/__manifest__.py delete mode 100644 estate_crm/models/__init__.py delete mode 100644 estate_crm/models/estate_property.py diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py deleted file mode 100644 index b0f26a9a602..00000000000 --- a/awesome_dashboard/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py deleted file mode 100644 index 58817d5fbd4..00000000000 --- a/awesome_dashboard/__manifest__.py +++ /dev/null @@ -1,35 +0,0 @@ -{ - 'name': "Awesome Dashboard", - - 'summary': """ - Starting module for "Discover the JS framework, chapter 2: Build a dashboard" - """, - - 'description': """ - Starting module for "Discover the JS framework, chapter 2: Build a dashboard" - """, - - 'author': "Odoo", - 'website': "https://www.odoo.com/", - 'category': 'Tutorials', - 'version': '0.1', - 'application': True, - 'installable': True, - 'depends': ['base', 'web', 'mail', 'crm'], - - 'data': [ - 'views/views.xml', - ], - - 'assets': { - 'web.assets_backend': [ - 'awesome_dashboard/static/src/**/*', - ], - - 'awesome_dashboard.dashboard': [ - 'awesome_dashboard/static/src/dashboard/**/*' - ], - }, - - 'license': 'AGPL-3' -} diff --git a/awesome_dashboard/controllers/__init__.py b/awesome_dashboard/controllers/__init__.py deleted file mode 100644 index 457bae27e11..00000000000 --- a/awesome_dashboard/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file diff --git a/awesome_dashboard/controllers/controllers.py b/awesome_dashboard/controllers/controllers.py deleted file mode 100644 index 05977d3bd7f..00000000000 --- a/awesome_dashboard/controllers/controllers.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- - -import logging -import random - -from odoo import http -from odoo.http import request - -logger = logging.getLogger(__name__) - -class AwesomeDashboard(http.Controller): - @http.route('/awesome_dashboard/statistics', type='jsonrpc', auth='user') - def get_statistics(self): - """ - Returns a dict of statistics about the orders: - 'average_quantity': the average number of t-shirts by order - 'average_time': the average time (in hours) elapsed between the - moment an order is created, and the moment is it sent - 'nb_cancelled_orders': the number of cancelled orders, this month - 'nb_new_orders': the number of new orders, this month - 'total_amount': the total amount of orders, this month - """ - - return { - 'average_quantity': random.randint(4, 12), - 'average_time': random.randint(4, 123), - 'nb_cancelled_orders': random.randint(0, 50), - 'nb_new_orders': random.randint(10, 200), - 'orders_by_size': { - 'm': random.randint(0, 150), - 's': random.randint(0, 150), - 'xl': random.randint(0, 150), - }, - 'total_amount': random.randint(100, 1000) - } - diff --git a/awesome_dashboard/static/src/dashboard/dashboard.js b/awesome_dashboard/static/src/dashboard/dashboard.js deleted file mode 100644 index 2c988a966f1..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard.js +++ /dev/null @@ -1,102 +0,0 @@ -/** @odoo-module **/ -import { Component, useState } from "@odoo/owl"; -import { registry } from "@web/core/registry"; -import { Layout } from "@web/search/layout"; -import { useService } from "@web/core/utils/hooks"; -import { DashboardItem } from "./dashboard_item/dashboard_item"; -import { Dialog } from "@web/core/dialog/dialog"; -import { CheckBox } from "@web/core/checkbox/checkbox"; -import { browser } from "@web/core/browser/browser"; -import "./statistics_service"; -import "./dashboard_items"; - -class AwesomeDashboard extends Component { - static template = "awesome_dashboard.AwesomeDashboard"; - static components = { Layout, DashboardItem }; - - setup() { - this.action = useService("action"); - this.dialog = useService("dialog"); - this.display = { controlPanel: {} }; - this.stats = useService("awesome_dashboard.statistics"); - - this.state = useState({ - disabledItems: JSON.parse( - browser.localStorage.getItem("disabledDashboardItems") || "[]" - ) - }); - - this.allItems = registry - .category("awesome_dashboard.items") - .getAll(); - } - - get items() { - return this.allItems.filter( - item => !this.state.disabledItems.includes(item.id) - ); - } - - openConfiguration() { - this.dialog.add(ConfigurationDialog, { - items: this.allItems, - disabledItems: this.state.disabledItems, - onUpdate: this.updateConfiguration.bind(this), - }); - } - - updateConfiguration(newDisabledItems) { - this.state.disabledItems = newDisabledItems; - browser.localStorage.setItem( - "disabledDashboardItems", - JSON.stringify(newDisabledItems) - ); - } - - openCustomers() { - this.action.doAction("base.action_partner_form"); - } - - openLeads() { - this.action.doAction({ - type: "ir.actions.act_window", - name: "Leads", - res_model: "crm.lead", - views: [[false, "list"], [false, "form"]], - }); - } -} - -class ConfigurationDialog extends Component { - static template = "awesome_dashboard.ConfigurationDialog"; - static components = { Dialog, CheckBox }; - static props = ["close", "items", "disabledItems", "onUpdate"]; - - setup() { - const disabled = Array.isArray(this.props.disabledItems) - ? this.props.disabledItems - : []; - - this.items = useState( - this.props.items.map(item => ({ - ...item, - enabled: !disabled.includes(item.id), - })) - ); - } - - onChange(checked, item) { - item.enabled = checked; - const disabled = this.items - .filter(i => !i.enabled) - .map(i => i.id); - this.props.onUpdate(disabled); - } - - done() { - this.props.close(); - } -} -registry - .category("lazy_components") - .add("awesome_dashboard.AwesomeDashboard", AwesomeDashboard); diff --git a/awesome_dashboard/static/src/dashboard/dashboard.scss b/awesome_dashboard/static/src/dashboard/dashboard.scss deleted file mode 100644 index ffd265b1982..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard.scss +++ /dev/null @@ -1,10 +0,0 @@ -.o_dashboard { - background-color: rgb(16, 18, 20); -} - -@media (max-width: 768px) { - - .o_dashboard_item { - width: 100% !important; - } -} diff --git a/awesome_dashboard/static/src/dashboard/dashboard.xml b/awesome_dashboard/static/src/dashboard/dashboard.xml deleted file mode 100644 index 4df87c37ab1..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js deleted file mode 100644 index 818c69c580f..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@odoo/owl"; - -export class DashboardItem extends Component { - static template = "awesome_dashboard.DashboardItem"; - - static slots = { - default: {}, - }; -} diff --git a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml b/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml deleted file mode 100644 index 82e3f8055d3..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard_item/dashboard_item.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - -
- -
- -
-
diff --git a/awesome_dashboard/static/src/dashboard/dashboard_items.js b/awesome_dashboard/static/src/dashboard/dashboard_items.js deleted file mode 100644 index 695b557c2f8..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard_items.js +++ /dev/null @@ -1,71 +0,0 @@ -/** @odoo-module **/ -import { dashboardItemRegistry } from "./dashboard_registry"; -import { NumberCard } from "./number_card/number_card"; -import { PieChartCard } from "./pie_chart_card/pie_chart_card"; -import { _t } from "@web/core/l10n/translation"; - -dashboardItemRegistry.add("new_orders", { - id: "new_orders", - description: _t("New orders this month"), - Component: NumberCard, - size: 1, - props: (data) => ({ - title: _t("Number of new orders"), - value: data?.nb_new_orders || 0, - }), -}); - -dashboardItemRegistry.add("total_amount", { - id: "total_amount", - description: _t("Total amount orders this month"), - Component: NumberCard, - size: 1, - props: (data) => ({ - title: _t("Total amount"), - value: data?.total_amount || 0, - }), -}); - -dashboardItemRegistry.add("avg_quantity", { - id: "avg_quantity", - description: _t("Average amount of t-shirt"), - Component: NumberCard, - size: 1, - props: (data) => ({ - title: _t("Average quantity"), - value: data?.average_quantity || 0, - }), -}); - -dashboardItemRegistry.add("average_time", { - id: "average_time", - description: _t("Average time new to sent/cancelled"), - Component: NumberCard, - size: 1, - props: (data) => ({ - title: _t("Average time"), - value: data?.average_time || 0, - }), -}); - -dashboardItemRegistry.add("nb_cancelled_orders", { - id: "nb_cancelled_orders", - description: _t("Cancelled orders this month"), - Component: NumberCard, - size: 1, - props: (data) => ({ - title: _t("Number of cancelled orders"), - value: data?.nb_cancelled_orders || 0, - }), -}); - -dashboardItemRegistry.add("pie_chart", { - id: "pie_chart", - description: _t("Shirt orders by size"), - Component: PieChartCard, - size: 2, - props: (data) => ({ - title: _t("Orders by size"), - data: data?.orders_by_size || {}, - }), -}); diff --git a/awesome_dashboard/static/src/dashboard/dashboard_registry.js b/awesome_dashboard/static/src/dashboard/dashboard_registry.js deleted file mode 100644 index e3fe596f7ad..00000000000 --- a/awesome_dashboard/static/src/dashboard/dashboard_registry.js +++ /dev/null @@ -1,3 +0,0 @@ -import { registry } from "@web/core/registry"; - -export const dashboardItemRegistry = registry.category("awesome_dashboard.items"); diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.js b/awesome_dashboard/static/src/dashboard/number_card/number_card.js deleted file mode 100644 index 95cc39c8b33..00000000000 --- a/awesome_dashboard/static/src/dashboard/number_card/number_card.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@odoo/owl"; - -export class NumberCard extends Component { - static template = "awesome_dashboard.NumberCard"; - static props = { - title: String, - value: { type: Number, optional: true }, - }; -} diff --git a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml b/awesome_dashboard/static/src/dashboard/number_card/number_card.xml deleted file mode 100644 index 1da288b98a9..00000000000 --- a/awesome_dashboard/static/src/dashboard/number_card/number_card.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - -
-
-

-
-
-
diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js deleted file mode 100644 index 4fb8c3bacaa..00000000000 --- a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Component, onWillStart, onMounted, useRef } from "@odoo/owl"; -import { loadJS } from "@web/core/assets"; - -export class PieChart extends Component { - static template = "awesome_dashboard.PieChart"; - - static props = { - data: Object, - }; - - setup() { - this.canvasRef = useRef("chart"); - onWillStart(async () => { - await loadJS("/web/static/lib/Chart/Chart.js"); - }); - onMounted(() => { - this.renderChart(); - }); - } - - renderChart() { - const ctx = this.canvasRef.el.getContext("2d"); - - new Chart(ctx, { - type: "pie", - data: { - labels: Object.keys(this.props.data), - datasets: [{ - data: Object.values(this.props.data), - }], - }, - }); - } -} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml b/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml deleted file mode 100644 index 6d356ee58fb..00000000000 --- a/awesome_dashboard/static/src/dashboard/pie_chart/pie_chart.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js deleted file mode 100644 index 23aa93ec2c3..00000000000 --- a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Component } from "@odoo/owl"; -import { PieChart } from "../pie_chart/pie_chart"; - -export class PieChartCard extends Component { - static template = "awesome_dashboard.PieChartCard"; - static components = { PieChart } - static props = { - title: String, - data: Object, - }; -} diff --git a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml b/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml deleted file mode 100644 index f22aac77842..00000000000 --- a/awesome_dashboard/static/src/dashboard/pie_chart_card/pie_chart_card.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - -
-
- -
-
- -
diff --git a/awesome_dashboard/static/src/dashboard/statistics_service.js b/awesome_dashboard/static/src/dashboard/statistics_service.js deleted file mode 100644 index fe717b5e350..00000000000 --- a/awesome_dashboard/static/src/dashboard/statistics_service.js +++ /dev/null @@ -1,26 +0,0 @@ -/** @odoo-module **/ -import { registry } from "@web/core/registry"; -import { rpc } from "@web/core/network/rpc"; -import { reactive } from "@odoo/owl"; - -const statisticsService = { - name: "awesome_dashboard.statistics", - - start() { - const stats = reactive({}); - - async function loadStatistics() { - const data = await rpc("/awesome_dashboard/statistics"); - Object.assign(stats, data); - } - - loadStatistics(); - setInterval(loadStatistics, 10 * 60 * 1000); - - return stats; - }, -}; - -registry - .category("services") - .add("awesome_dashboard.statistics", statisticsService); diff --git a/awesome_dashboard/static/src/dashboard_action.js b/awesome_dashboard/static/src/dashboard_action.js deleted file mode 100644 index 52decad3f86..00000000000 --- a/awesome_dashboard/static/src/dashboard_action.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Component } from "@odoo/owl"; -import { LazyComponent } from "@web/core/assets"; -import { registry } from "@web/core/registry"; - -export class DashboardAction extends Component { - static components = { LazyComponent }; - static template = "awesome_dashboard.DashboardLoader"; -} - -registry.category("actions").add("awesome_dashboard.dashboard", DashboardAction); diff --git a/awesome_dashboard/static/src/dashboard_action.xml b/awesome_dashboard/static/src/dashboard_action.xml deleted file mode 100644 index 5beaede125b..00000000000 --- a/awesome_dashboard/static/src/dashboard_action.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/awesome_dashboard/views/views.xml b/awesome_dashboard/views/views.xml deleted file mode 100644 index 47fb2b6f258..00000000000 --- a/awesome_dashboard/views/views.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - Dashboard - awesome_dashboard.dashboard - - - - - - diff --git a/awesome_owl/__init__.py b/awesome_owl/__init__.py deleted file mode 100644 index 457bae27e11..00000000000 --- a/awesome_owl/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py deleted file mode 100644 index 55002ab81de..00000000000 --- a/awesome_owl/__manifest__.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -{ - 'name': "Awesome Owl", - - 'summary': """ - Starting module for "Discover the JS framework, chapter 1: Owl components" - """, - - 'description': """ - Starting module for "Discover the JS framework, chapter 1: Owl components" - """, - - 'author': "Odoo", - 'website': "https://www.odoo.com", - - # Categories can be used to filter modules in modules listing - # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml - # for the full list - 'category': 'Tutorials', - 'version': '0.1', - - # any module necessary for this one to work correctly - 'depends': ['base', 'web'], - 'application': True, - 'installable': True, - 'data': [ - 'views/templates.xml', - ], - 'assets': { - 'awesome_owl.assets_playground': [ - ('include', 'web._assets_helpers'), - ('include', 'web._assets_backend_helpers'), - 'web/static/src/scss/pre_variables.scss', - 'web/static/lib/bootstrap/scss/_variables.scss', - 'web/static/lib/bootstrap/scss/_maps.scss', - ('include', 'web._assets_bootstrap'), - ('include', 'web._assets_core'), - 'web/static/src/libs/fontawesome/css/font-awesome.css', - 'awesome_owl/static/src/**/*', - ], - }, - 'license': 'AGPL-3' -} diff --git a/awesome_owl/controllers/__init__.py b/awesome_owl/controllers/__init__.py deleted file mode 100644 index 457bae27e11..00000000000 --- a/awesome_owl/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- - -from . import controllers \ No newline at end of file diff --git a/awesome_owl/controllers/controllers.py b/awesome_owl/controllers/controllers.py deleted file mode 100644 index bccfd6fe283..00000000000 --- a/awesome_owl/controllers/controllers.py +++ /dev/null @@ -1,10 +0,0 @@ -from odoo import http -from odoo.http import request, route - -class OwlPlayground(http.Controller): - @http.route(['/awesome_owl'], type='http', auth='public') - def show_playground(self): - """ - Renders the owl playground page - """ - return request.render('awesome_owl.playground') diff --git a/awesome_owl/static/src/card/card.js b/awesome_owl/static/src/card/card.js deleted file mode 100644 index b159b534b05..00000000000 --- a/awesome_owl/static/src/card/card.js +++ /dev/null @@ -1,19 +0,0 @@ -/** @odoo-module **/ -import { Component, useState } from "@odoo/owl"; - -export class Card extends Component { - static template = "awesome_owl.Card"; - - static props = { - title: { type: String }, - slots: { type: Object, optional: true }, - }; - - setup() { - this.state = useState({ isOpen: true }); - } - - toggleContent() { - this.state.isOpen = !this.state.isOpen; - } -} diff --git a/awesome_owl/static/src/card/card.xml b/awesome_owl/static/src/card/card.xml deleted file mode 100644 index 145406a5024..00000000000 --- a/awesome_owl/static/src/card/card.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - -
- -
- - - - - - - − - + - - -
- -
- -
- -
-
-
diff --git a/awesome_owl/static/src/counter/counter.js b/awesome_owl/static/src/counter/counter.js deleted file mode 100644 index b509cd0672a..00000000000 --- a/awesome_owl/static/src/counter/counter.js +++ /dev/null @@ -1,21 +0,0 @@ -/** @odoo-module **/ -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({ value: 0 }); - } - - increment() { - this.state.value++; - if (this.props.onChange) { - this.props.onChange(this.state.value); - } - } -} diff --git a/awesome_owl/static/src/counter/counter.xml b/awesome_owl/static/src/counter/counter.xml deleted file mode 100644 index eb3e6b97472..00000000000 --- a/awesome_owl/static/src/counter/counter.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - -
- - - Counter: - - - - -
-
-
diff --git a/awesome_owl/static/src/main.js b/awesome_owl/static/src/main.js deleted file mode 100644 index 1aaea902b55..00000000000 --- a/awesome_owl/static/src/main.js +++ /dev/null @@ -1,12 +0,0 @@ -import { whenReady } from "@odoo/owl"; -import { mountComponent } from "@web/env"; -import { Playground } from "./playground"; - -const config = { - dev: true, - name: "Owl Tutorial" -}; - -// Mount the Playground component when the document.body is ready -whenReady(() => mountComponent(Playground, document.body, config)); - diff --git a/awesome_owl/static/src/playground.js b/awesome_owl/static/src/playground.js deleted file mode 100644 index dbb268c4d18..00000000000 --- a/awesome_owl/static/src/playground.js +++ /dev/null @@ -1,20 +0,0 @@ -/** @odoo-module **/ -import { Component, useState, markup } from "@odoo/owl"; -import { Counter } from "./counter/counter"; -import { Card } from "./card/card"; -import { TodoList } from "./todo_list/todo_list"; - -export class Playground extends Component { - static template = "awesome_owl.Playground"; - static components = { Counter, Card, TodoList }; - - setup() { - this.state = useState({ sum: 0 }); - this.safeHtml = markup("
some content
"); - this.unsafeHtml = "
some content
"; - } - - incrementSum() { - this.state.sum++; - } -} diff --git a/awesome_owl/static/src/playground.xml b/awesome_owl/static/src/playground.xml deleted file mode 100644 index b06c0be9cb3..00000000000 --- a/awesome_owl/static/src/playground.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - -
- -

Hello Counter

- - - - -

- The sum is: -

- -
- - This is simple text inside card - - - - -
- -
- -
- -
- -
-
diff --git a/awesome_owl/static/src/todo_list/todo_item.js b/awesome_owl/static/src/todo_list/todo_item.js deleted file mode 100644 index efc3053a547..00000000000 --- a/awesome_owl/static/src/todo_list/todo_item.js +++ /dev/null @@ -1,19 +0,0 @@ -/** @odoo-module **/ -import { Component } from "@odoo/owl"; - -export class TodoItem extends Component { - static template = "awesome_owl.TodoItem"; - - static props = { - todo: { - type: Object, - shape: { - id: Number, - description: String, - isCompleted: Boolean, - }, - }, - toggleState: { type: Function }, - removeTodo: { type: Function }, - }; -} diff --git a/awesome_owl/static/src/todo_list/todo_item.xml b/awesome_owl/static/src/todo_list/todo_item.xml deleted file mode 100644 index 14126c9e69d..00000000000 --- a/awesome_owl/static/src/todo_list/todo_item.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - -
- - - - - . - - - - - -
-
-
diff --git a/awesome_owl/static/src/todo_list/todo_list.js b/awesome_owl/static/src/todo_list/todo_list.js deleted file mode 100644 index 04dfb0f57c7..00000000000 --- a/awesome_owl/static/src/todo_list/todo_list.js +++ /dev/null @@ -1,45 +0,0 @@ -/** @odoo-module **/ -import { Component, useState, useRef, onMounted } from "@odoo/owl"; -import { TodoItem } from "./todo_item"; - -export class TodoList extends Component { - static template = "awesome_owl.TodoList"; - static components = { TodoItem }; - - setup() { - this.todos = useState([]); - this.nextId = 1; - this.inputRef = useRef("todoInput"); - - onMounted(() => { - this.inputRef.el.focus(); - }); - } - - addTodo(ev) { - if (ev.keyCode === 13) { - const description = ev.target.value.trim(); - if (!description) return; - this.todos.push({ - id: this.nextId++, - description: description, - isCompleted: false, - }); - ev.target.value = ""; - } - } - - toggleState(todoId) { - const todo = this.todos.find(t => t.id === todoId); - if (todo) { - todo.isCompleted = !todo.isCompleted; - } - } - - removeTodo(todoId) { - const index = this.todos.findIndex(t => t.id === todoId); - if (index >= 0) { - this.todos.splice(index, 1); - } - } -} diff --git a/awesome_owl/static/src/todo_list/todo_list.xml b/awesome_owl/static/src/todo_list/todo_list.xml deleted file mode 100644 index 7b19de79303..00000000000 --- a/awesome_owl/static/src/todo_list/todo_list.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - -
- -

- Todo List -

- - - - - - - -
-
-
diff --git a/awesome_owl/views/templates.xml b/awesome_owl/views/templates.xml deleted file mode 100644 index aa54c1a7241..00000000000 --- a/awesome_owl/views/templates.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - diff --git a/bom_forecast/__init__.py b/bom_forecast/__init__.py new file mode 100644 index 00000000000..9186ee3ad24 --- /dev/null +++ b/bom_forecast/__init__.py @@ -0,0 +1 @@ +from . import model diff --git a/bom_forecast/__manifest__.py b/bom_forecast/__manifest__.py new file mode 100644 index 00000000000..6ee320ce17e --- /dev/null +++ b/bom_forecast/__manifest__.py @@ -0,0 +1,16 @@ +{ + "name": "bom_forecast", + "version": "1.0", + "depends": ["mrp", "purchase"], + "author": "soham", + "category": "Tutorials", + "license": "LGPL-3", + 'installable': True, + 'auto_install': True, + 'application': True, + "assets": { + "web.assets_backend": [ + "bom_forecast/static/src/**/*", + ], + }, +} diff --git a/bom_forecast/model/__init__.py b/bom_forecast/model/__init__.py new file mode 100644 index 00000000000..98974148e0e --- /dev/null +++ b/bom_forecast/model/__init__.py @@ -0,0 +1 @@ +from . import report_mrp_bom_structure diff --git a/bom_forecast/model/report_mrp_bom_structure.py b/bom_forecast/model/report_mrp_bom_structure.py new file mode 100644 index 00000000000..a406162251c --- /dev/null +++ b/bom_forecast/model/report_mrp_bom_structure.py @@ -0,0 +1,15 @@ +from odoo import _, models + + +class ReportMrpReportBomStructure(models.AbstractModel): + _inherit = "report.mrp.report_bom_structure" + + def _get_bom_data(self, *args, **kwargs): + result = super()._get_bom_data(*args, **kwargs) + if result.get("level") == 0: + qty = int(result.get("producible_qty") or 0) + if qty > 0: + result["status"] = _("%(qty)s Ready To Produce", qty=qty) + else: + result["status"] = _("No Ready To Produce") + return result diff --git a/bom_forecast/static/src/bom_overview_line.js b/bom_forecast/static/src/bom_overview_line.js new file mode 100644 index 00000000000..058723a2187 --- /dev/null +++ b/bom_forecast/static/src/bom_overview_line.js @@ -0,0 +1,28 @@ +import { patch } from "@web/core/utils/patch"; +import { BomOverviewLine } from "@mrp/components/bom_overview_line/mrp_bom_overview_line"; + +patch(BomOverviewLine.prototype, { + + get statusBackgroundColor() { + switch (this.data.availability_state) { + case "available": return "text-bg-success"; + case "expected": + case "estimated": + if (this.data.level === 0) return "text-bg-dark"; + return "border border-warning text-warning"; + case "unavailable": return "text-bg-danger"; + default: return "text-bg-dark"; + } + }, + + get statusDisplay() { + if (this.data.level > 0 && + this.data.availability_display && + this.data.availability_display.includes("Estimated")) { + return this.data.availability_display.replace( + "Estimated", "Expected" + ); + } + return this.data.availability_display; + } +}); diff --git a/bom_forecast/static/src/bom_overview_line.xml b/bom_forecast/static/src/bom_overview_line.xml new file mode 100644 index 00000000000..c3dc59fb628 --- /dev/null +++ b/bom_forecast/static/src/bom_overview_line.xml @@ -0,0 +1,38 @@ + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+
+
+
diff --git a/bom_forecast/static/src/mrp_bom_overview_table.xml b/bom_forecast/static/src/mrp_bom_overview_table.xml new file mode 100644 index 00000000000..151a81b4662 --- /dev/null +++ b/bom_forecast/static/src/mrp_bom_overview_table.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/estate/__init__.py b/estate/__init__.py deleted file mode 100644 index 0650744f6bc..00000000000 --- a/estate/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import models diff --git a/estate/__manifest__.py b/estate/__manifest__.py deleted file mode 100644 index 5cdf7aed65f..00000000000 --- a/estate/__manifest__.py +++ /dev/null @@ -1,22 +0,0 @@ -{ - 'name': 'Real Estate', - 'author': 'soham', - 'license': 'LGPL-3', - 'depends': ['base', 'calendar', 'mail', 'sale'], - 'data': [ - 'security/ir.model.access.csv', - 'data/mail_template_data.xml', - 'data/ir_cron.xml', - 'views/estate_property_visit_views.xml', - 'views/estate_property_issue_views.xml', - 'views/estate_property_offer_views.xml', - 'views/estate_property_type_views.xml', - 'views/estate_property_tag_views.xml', - 'views/estate_property_wizard_views.xml', - 'views/estate_property_views.xml', - 'views/res_users_views.xml', - 'views/estate_menus.xml', - ], - 'installable': True, - 'application': True, -} diff --git a/estate/data/ir_cron.xml b/estate/data/ir_cron.xml deleted file mode 100644 index 9d1cf480adc..00000000000 --- a/estate/data/ir_cron.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - Offer: Expired Offers Refused - - code - model._cron_automatic_refuse() - - 5 - minutes - - diff --git a/estate/data/mail_template_data.xml b/estate/data/mail_template_data.xml deleted file mode 100644 index dac0617beb4..00000000000 --- a/estate/data/mail_template_data.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - Estate: Property Sold - - {{object.name}} Sold - - {{ object.salesman_id.email_formatted }} - - -
-

Dear ,

-

Property - - has been sold.

-

Seller:

-

Selling Price:

-

Description:

-

Thank you!

-
-
-
-
-
diff --git a/estate/models/__init__.py b/estate/models/__init__.py deleted file mode 100644 index dfacdea237e..00000000000 --- a/estate/models/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from . import estate_property -from . import estate_property_type -from . import estate_property_tag -from . import estate_property_offer -from . import res_users -from . import estate_property_visit -from . import estate_property_issue -from . import estate_property_wizard diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py deleted file mode 100644 index 41b538fe1e7..00000000000 --- a/estate/models/estate_property.py +++ /dev/null @@ -1,261 +0,0 @@ -from odoo import api, Command, fields, models -from odoo.exceptions import UserError, ValidationError -from odoo.tools.float_utils import float_compare, float_is_zero - - -class EstateProperty(models.Model): - _name = 'estate.property' - _inherit = ['mail.thread', 'mail.activity.mixin'] - _description = "Real Estate Property" - _order = "id desc" - - name = fields.Char(required=True) - expected_price = fields.Float(required=True) - description = fields.Text() - postcode = fields.Char(tracking=1) - date_availability = fields.Date(copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3)) - selling_price = fields.Float(readonly=True, copy=False) - bedrooms = fields.Integer(default=2) - living_area = fields.Integer() - sold_date = fields.Date(string="Sold Date", readonly=True, copy=False) - active = fields.Boolean(default=True) - facades = fields.Integer() - garage = fields.Boolean() - garden = fields.Boolean() - garden_area = fields.Integer() - garden_orientation = fields.Selection( - string="Orientation", - selection=[('north', "North"), ('south', "South"), ('east', "East"), ('west', "West")], - help="The direction the garden faces." - ) - state = fields.Selection( - selection=[ - ('new', "New"), - ('offer_received', "Offer Received"), - ('offer_accepted', "Offer Accepted"), - ('sold', "Sold"), - ('canceled', "Cancelled"), - ], - string="Status", - required=True, - copy=False, - default='new', - ) - property_type_id = fields.Many2one( - "estate.property.type", - string="Property Type", - ) - buyer_id = fields.Many2one( - "res.partner", - string="Buyer", - copy=False, - ) - salesman_id = fields.Many2one( - "res.users", - string="Salesman", - default=lambda self: self.env.user, - ) - tag_ids = fields.Many2many( - "estate.property.tag", - string="Tags", - compute="_compute_tags", - store=True, - ) - offer_ids = fields.One2many( - "estate.property.offer", - "property_id", - string="Offers", - copy=True - ) - issue_ids = fields.One2many( - 'estate.property.issue', - 'property_id', - ) - visit_ids = fields.One2many( - "estate.property.visit", - "property_id", - string="Visits", - ) - total_area = fields.Integer( - compute="_compute_total_area", - string="Total Area (sqm)", - ) - best_price = fields.Float( - compute="_compute_best_price", - string="Best Offer", - store=True, - ) - has_suspicious_offers = fields.Boolean( - string="Has Suspicious Offers", - compute="_compute_has_suspicious_offers" - ) - visit_count = fields.Integer( - string="Visits Count", - compute="_compute_visit_count", - store=True, - ) - - _check_expected_price = models.Constraint( - 'CHECK(expected_price > 0)', - 'Expected price must be strictly positive!', - ) - _check_selling_price = models.Constraint( - 'CHECK(selling_price >= 0)', - 'Selling price must be positive!', - ) - - @api.depends("visit_ids") - def _compute_visit_count(self): - for record in self: - record.visit_count = len(record.visit_ids) - - @api.depends("living_area", "garden_area") - def _compute_total_area(self): - for record in self: - record.total_area = (record.living_area + record.garden_area - ) - - @api.depends("offer_ids.price") - def _compute_best_price(self): - for record in self: - if record.offer_ids: - record.best_price = max( - record.offer_ids.mapped("price") - ) - else: - record.best_price = 0.0 - - @api.depends("offer_ids.is_suspicious") - def _compute_has_suspicious_offers(self): - for record in self: - record.has_suspicious_offers = any( - offer.is_suspicious for offer in record.offer_ids - ) - - @api.depends('expected_price', 'offer_ids', 'sold_date', 'date_availability', 'state') - def _compute_tags(self): - for record in self: - tag_records = self.env['estate.property.tag'] - - if record.expected_price > 200000: - tag_records |= self._get_or_create_tag('High Value') - - if (record.state == 'sold' - and record.sold_date - and record.date_availability - and (record.sold_date - record.date_availability).days <= 10): - tag_records |= self._get_or_create_tag('Quick Sale') - - if len(record.offer_ids) < 2: - tag_records |= self._get_or_create_tag('Low Interest') - - record.tag_ids = tag_records - - @api.onchange("garden") - def _onchange_garden(self): - if self.garden: - self.garden_area = 10 - self.garden_orientation = "north" - else: - self.garden_area = 0 - self.garden_orientation = False - - @api.constrains('selling_price', 'expected_price') - def _check_seling_price(self): - for record in self: - if float_is_zero( - record.selling_price, - precision_digits=2 - ): - continue - if float_compare( - record.selling_price, - record.expected_price * 0.9, - precision_digits=2 - ) < 0: - raise ValidationError("Selling price cannot be lower than 90%" - "of expected price!") - - def action_sold(self): - for record in self: - if record.state != 'offer_accepted': - raise UserError("Accept the property first") - if record.state == 'canceled': - raise UserError("Cancelled property cannot be sold!") - for rec in record.issue_ids: - if rec.staty != 'resolved' and rec.priority == 'high': - raise UserError( - "Cannot sell the property, please solve the issues" - ) - record.state = 'sold' - record.sold_date = fields.Date.today() - - ctx = { - 'default_model': 'estate.property', - 'default_res_ids': self.ids, - 'default_template_id': self.env.ref('estate.email_template_estate_property_sold').id, - 'default_partner_ids': [self.buyer_id.id, self.salesman_id.partner_id.id], - } - - return { - 'name': 'Sold', - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'mail.compose.message', - 'views': [(False, 'form')], - 'view_id': False, - 'target': 'new', - 'context': ctx, - } - - def action_cancel(self): - for record in self: - if record.state == 'sold': - raise UserError( - "Sold property cannot be cancelled!" - ) - record.state = 'canceled' - return True - - def action_create_quotations(self): - if not self.offer_ids: - raise UserError("No offers found") - - for offer in self.offer_ids: - if offer.quotation_id: - continue - quotation = self.env["sale.order"].create({ - "partner_id": offer.partner_id.id, - "origin": self.name, - "order_line": [ - Command.create({ - "name": self.name, - "product_uom_qty": 1, - "price_unit": offer.price, - }) - ], - }) - - offer.quotation_id = quotation.id - return True - - def _search_tag(self, tag_name): - return self.env['estate.property.tag'].search( - [('name', '=', tag_name)], limit=1 - ) - - def _create_tag(self, tag_name): - return self.env['estate.property.tag'].create( - {'name': tag_name} - ) - - def _get_or_create_tag(self, tag_name): - return self._search_tag(tag_name) or self._create_tag(tag_name) - - @api.ondelete(at_uninstall=False) - def _unlink_if_new_or_canceled(self): - for record in self: - if record.state not in ('new', 'canceled'): - raise UserError( - "Only new and cancelled properties can be deleted!" - ) diff --git a/estate/models/estate_property_issue.py b/estate/models/estate_property_issue.py deleted file mode 100644 index 05066253f3b..00000000000 --- a/estate/models/estate_property_issue.py +++ /dev/null @@ -1,105 +0,0 @@ -from datetime import timedelta -from odoo import api, fields, models - - -class EstatePropertyIssue(models.Model): - _name = "estate.property.issue" - _description = "Property Issue" - - name = fields.Char(required=True) - property_id = fields.Many2one('estate.property', required=True) - - buyer_id = fields.Many2one( - "res.partner", - string="Reported By", - copy=False, - ) - - salesman_id = fields.Many2one( - "res.users", - string="Assigned By", - ) - - issue_type = fields.Selection( - selection=[ - ('plumbing', "Plumbing"), - ('electrical', "Electrical"), - ('structural', "Structural"), - ('others', "Others"), - ], - required=True, - ) - - staty = fields.Selection( - selection=[ - ('new', "New"), - ('in_progress', "In Progress"), - ('resolved', "Resolved"), - ('canceled', "Canceled"), - ], - default='new' - ) - - priority = fields.Selection( - selection=[ - ('low', "Low"), - ('medium', "Medium"), - ('high', "High"), - ], - compute="_compute_priority", - store=True, - readonly=True, - ) - - resolved_date = fields.Datetime() - description_issue = fields.Text() - - is_overdue = fields.Boolean( - compute="_compute_is_overdue", - store=True, - string="Overdue", - ) - - @api.depends("issue_type") - def _compute_priority(self): - for rec in self: - if rec.issue_type in ('others', 'plumbing'): - rec.priority = 'low' - elif rec.issue_type == 'structural': - rec.priority = 'high' - elif rec.issue_type == 'electrical': - rec.priority = 'medium' - else: - rec.priority = False - - @api.depends('create_date', 'priority', 'resolved_date') - def _compute_is_overdue(self): - for rec in self: - if not rec.create_date: - rec.is_overdue = False - continue - - if rec.priority == 'high': - days = 2 - elif rec.priority == 'medium': - days = 5 - else: - days = 10 - - deadline = rec.create_date + timedelta(days=days) - end_time = rec.resolved_date or fields.Datetime.now() - rec.is_overdue = end_time > deadline - - @api.onchange("salesman_id") - def _onchange_salesman_id(self): - if self.salesman_id: - self.staty = 'in_progress' - - def action_resolve(self): - for rec in self: - rec.staty = 'resolved' - rec.resolved_date = fields.Datetime.now() - - def action_cancel(self): - for rec in self: - rec.staty = 'canceled' diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py deleted file mode 100644 index e9de6781318..00000000000 --- a/estate/models/estate_property_offer.py +++ /dev/null @@ -1,149 +0,0 @@ -from datetime import timedelta -from odoo import api, fields, models -from odoo.exceptions import UserError - - -class EstatePropertyOffer(models.Model): - _name = "estate.property.offer" - _description = "Real Estate Property Offer" - _order = "price desc" - - price = fields.Float() - status = fields.Selection( - selection=[ - ('accepted', "Accepted"), - ('refused', "Refused"), - ], - copy=True - ) - partner_id = fields.Many2one( - "res.partner", - string="Buyer", - required=True - ) - property_id = fields.Many2one( - "estate.property", - string="Property", - required=True - ) - property_type_id = fields.Many2one( - "estate.property.type", - related="property_id.property_type_id", - string="Property Type", - store=True - ) - validity = fields.Integer( - string="Validity (days)", - default=7, - ) - quotation_id = fields.Many2one( - "sale.order", - string="Quotation", - readonly=True, - copy=False - ) - date_deadline = fields.Date( - string="Deadline", - compute="_compute_date_deadline", - inverse="_set_date_deadline", - ) - is_suspicious = fields.Boolean( - string="Suspicious", - default=False, - readonly=True, - copy=False, - compute="_compute_is_suspicious", - store=True, - ) - - _check_price = models.Constraint( - 'CHECK(price > 0)', - 'Offer price must be strictly positive!', - ) - - @api.depends("validity", "create_date") - def _compute_date_deadline(self): - for record in self: - if record.create_date: - record.date_deadline = fields.Date.add( - record.create_date, - days=record.validity, - ) - else: - record.date_deadline = fields.Date.add( - fields.Date.today(), - days=record.validity, - ) - - def _set_date_deadline(self): - for record in self: - if record.create_date and record.date_deadline: - record.validity = ( - record.date_deadline - - record.create_date.date() - ).days - - @api.depends("partner_id", "create_date") - def _compute_is_suspicious(self): - for record in self: - if not record.create_date: - record.is_suspicious = False - continue - - start = (record.create_date - timedelta(minutes=5)) - end = (record.create_date + timedelta(minutes=5)) - recent_offers = self.search([ - ('partner_id', '=', record.partner_id.id), - ('create_date', '>=', start), - ('create_date', '<=', end), - ]) - - record.is_suspicious = len(recent_offers) >= 3 - - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - property_id = self.env['estate.property'].browse( - vals.get('property_id', False) - ) - if property_id.offer_ids: - max_offer = max(property_id.offer_ids.mapped('price')) - if vals.get('price', 0) < max_offer: - raise UserError( - "You cannot create an offer lower " - "than an existing offer of %.2f" % max_offer - ) - - property_id.state = 'offer_received' - - return super().create(vals_list) - - def action_accept(self): - if any(offer.status == 'accepted' for offer in self.property_id.offer_ids): - raise UserError("An offer has already been accepted for this property.") - self.status = 'accepted' - self.property_id.selling_price = self.price - self.property_id.buyer_id = self.partner_id - self.property_id.state = 'offer_accepted' - - for offer in self.property_id.offer_ids: - if offer.id != self.id: - offer.status = 'refused' - return True - - def action_refuse(self): - for record in self: - record.status = 'refused' - return True - - def _cron_automatic_refuse(self): - today = fields.Datetime.now() - offers = self.search([ - ('status', "not in", ["accepted", "refused"]), - ]) - for offer in offers: - expiry_date = offer.create_date + timedelta(days=offer.validity) - if expiry_date < today: - offer.write({ - 'status': 'refused' - }) diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py deleted file mode 100644 index eea1a456238..00000000000 --- a/estate/models/estate_property_tag.py +++ /dev/null @@ -1,15 +0,0 @@ -from odoo import fields, models - - -class EstatePropertyTag(models.Model): - _name = "estate.property.tag" - _description = "Real Estate Property Tag" - _order = "name" - - name = fields.Char(required=True) - color = fields.Integer(string="Color") - - _unique_name = models.Constraint( - 'UNIQUE(name)', - 'Property tag name must be unique!', - ) diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py deleted file mode 100644 index 0580974d8fa..00000000000 --- a/estate/models/estate_property_type.py +++ /dev/null @@ -1,40 +0,0 @@ -from odoo import api, fields, models - - -class EstatePropertyType(models.Model): - _name = "estate.property.type" - _description = "Real Estate Property Type" - _order = "sequence,name" - - name = fields.Char() - sequence = fields.Integer( - string="Sequence", - default=1, - help="Used to order property types manually." - ) - - property_ids = fields.One2many( - "estate.property", - "property_type_id", - string="Properties" - ) - offer_ids = fields.One2many( - "estate.property.offer", - "property_type_id", - string="Offers" - ) - - offer_count = fields.Integer( - string="Offers Count", - compute="_compute_offer_count" - ) - - _unique_name = models.Constraint( - 'UNIQUE(name)', - 'Property type name must be unique!', - ) - - @api.depends("offer_ids") - def _compute_offer_count(self): - for record in self: - record.offer_count = len(record.offer_ids) diff --git a/estate/models/estate_property_visit.py b/estate/models/estate_property_visit.py deleted file mode 100644 index 2522c0bb31d..00000000000 --- a/estate/models/estate_property_visit.py +++ /dev/null @@ -1,36 +0,0 @@ -from datetime import timedelta -from odoo import api, fields, models - - -class EstatePropertyVisit(models.Model): - _name = "estate.property.visit" - _description = "Property Visits" - _rec_name = "partner_id" - - property_id = fields.Many2one("estate.property", string="Property") - partner_id = fields.Many2one("res.partner", string="Customer") - visit_date = fields.Datetime(required=True) - stato = fields.Selection( - selection=[ - ('scheduled', "Scheduled"), - ('done', "Done"), - ], - default='scheduled' - ) - - _unique_date = models.Constraint( - 'UNIQUE(property_id, visit_date)', - 'Already Scheduled!!', - ) - - @api.model_create_multi - def create(self, vals_list): - records = super().create(vals_list) - - for record in records: - self.env['calendar.event'].create({ - 'name': "Visit", - 'start': record.visit_date, - 'stop': record.visit_date + timedelta(hours=1), - }) - return records diff --git a/estate/models/estate_property_wizard.py b/estate/models/estate_property_wizard.py deleted file mode 100644 index 42cb0528067..00000000000 --- a/estate/models/estate_property_wizard.py +++ /dev/null @@ -1,27 +0,0 @@ -from odoo import fields, models - - -class EstatePropertyWizard(models.TransientModel): - _name = "estate.property.wizard" - _description = "Create Offers" - - price = fields.Float() - - partner_id = fields.Many2one( - "res.partner", - string="Buyer", - copy=False, - ) - - def action_create_offers(self): - - properties = self.env["estate.property"].search([ - ("state", "not in", ["sold", "canceled", "offer_accepted"]) - ]) - for property in properties: - self.env["estate.property.offer"].create({ - "price": self.price, - "partner_id": self.partner_id.id, - "property_id": property.id, - }) - return True diff --git a/estate/models/res_users.py b/estate/models/res_users.py deleted file mode 100644 index f88e1bb18cc..00000000000 --- a/estate/models/res_users.py +++ /dev/null @@ -1,12 +0,0 @@ -from odoo import fields, models - - -class ResUsers(models.Model): - _inherit = "res.users" - - property_ids = fields.One2many( - "estate.property", - "salesman_id", - string="Assigned Properties", - domain=[('state', 'in', ['new', 'offer_received'])] - ) diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv deleted file mode 100644 index 051e7c3e2c4..00000000000 --- a/estate/security/ir.model.access.csv +++ /dev/null @@ -1,8 +0,0 @@ -id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1 -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 -access_estate_property_visit,estate.property.visit,model_estate_property_visit,base.group_user,1,1,1,1 -access_estate_property_issue,estate.property.issue,model_estate_property_issue,base.group_user,1,1,1,1 -access_estate_property_wizard,estate.property.wizard,model_estate_property_wizard,base.group_user,1,1,1,1 diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml deleted file mode 100644 index 25fad3f77c3..00000000000 --- a/estate/views/estate_menus.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/estate/views/estate_property_issue_views.xml b/estate/views/estate_property_issue_views.xml deleted file mode 100644 index 560e46741cf..00000000000 --- a/estate/views/estate_property_issue_views.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - Property Issue - estate.property.issue - list,form - - - - estate.property.issue.view.list - estate.property.issue - - - - - - - - - - - - - - - estate.property.issue.view.form - estate.property.issue - -
-
-
- -
-

- -

-
- - - - - - - - - - - - - - - - - - - -
-
-
-
- - - Property Issue - estate.property.issue - list,form - [('property_id', '=', active_id)] - {'default_property_id': active_id} - -
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml deleted file mode 100644 index 3dc3f36061e..00000000000 --- a/estate/views/estate_property_offer_views.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - Property Offers - estate.property.offer - list,form - - - - estate.property.offer.view.list - estate.property.offer - - - - - - - - - - - -
- -
-

- -

-
- - - - - - - - - - - -
- -
-
-
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml deleted file mode 100644 index 658f344eee8..00000000000 --- a/estate/views/estate_property_views.xml +++ /dev/null @@ -1,213 +0,0 @@ - - - Properties - estate.property - kanban,list,form - {'search_default_available': 1} - - - - estate.property.view.list - estate.property - - -
-
- - - - - - - - - - - -
-

- -

-
- - - - - - -
- - This property has some suspicious offers -
-
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -