diff --git a/.gitignore b/.gitignore
index b6e47617de..2f58826b2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker
.pyre/
+
+
+forme.txt
\ No newline at end of file
diff --git a/awesome_clicker/__init__.py b/awesome_clicker/__init__.py
index 40a96afc6f..e69de29bb2 100644
--- a/awesome_clicker/__init__.py
+++ b/awesome_clicker/__init__.py
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
diff --git a/awesome_clicker/__manifest__.py b/awesome_clicker/__manifest__.py
index e57ef4d5bb..2073c8b2d1 100644
--- a/awesome_clicker/__manifest__.py
+++ b/awesome_clicker/__manifest__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
{
'name': "Awesome Clicker",
diff --git a/awesome_dashboard/__init__.py b/awesome_dashboard/__init__.py
index b0f26a9a60..24dfd571b5 100644
--- a/awesome_dashboard/__init__.py
+++ b/awesome_dashboard/__init__.py
@@ -1,3 +1,3 @@
-# -*- coding: utf-8 -*-
+
from . import controllers
diff --git a/awesome_dashboard/__manifest__.py b/awesome_dashboard/__manifest__.py
index 31406e8add..6861aa3f7c 100644
--- a/awesome_dashboard/__manifest__.py
+++ b/awesome_dashboard/__manifest__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
{
'name': "Awesome Dashboard",
diff --git a/awesome_dashboard/controllers/__init__.py b/awesome_dashboard/controllers/__init__.py
index 457bae27e1..24dfd571b5 100644
--- a/awesome_dashboard/controllers/__init__.py
+++ b/awesome_dashboard/controllers/__init__.py
@@ -1,3 +1,3 @@
-# -*- coding: utf-8 -*-
-from . import controllers
\ No newline at end of file
+
+from . import controllers
diff --git a/awesome_dashboard/controllers/controllers.py b/awesome_dashboard/controllers/controllers.py
index 56d4a05128..90008c09dc 100644
--- a/awesome_dashboard/controllers/controllers.py
+++ b/awesome_dashboard/controllers/controllers.py
@@ -1,13 +1,13 @@
-# -*- 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='json', auth='user')
def get_statistics(self):
@@ -33,4 +33,3 @@ def get_statistics(self):
},
'total_amount': random.randint(100, 1000)
}
-
diff --git a/awesome_gallery/__init__.py b/awesome_gallery/__init__.py
index a0fdc10fe1..a9e3372262 100644
--- a/awesome_gallery/__init__.py
+++ b/awesome_gallery/__init__.py
@@ -1,2 +1,2 @@
-# -*- coding: utf-8 -*-
+
from . import models
diff --git a/awesome_gallery/__manifest__.py b/awesome_gallery/__manifest__.py
index a51f4e5d1c..937a27f171 100644
--- a/awesome_gallery/__manifest__.py
+++ b/awesome_gallery/__manifest__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
{
'name': "Gallery View",
'summary': """
diff --git a/awesome_gallery/models/__init__.py b/awesome_gallery/models/__init__.py
index 7f0930ee74..fbe96de672 100644
--- a/awesome_gallery/models/__init__.py
+++ b/awesome_gallery/models/__init__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
# import filename_python_file_within_folder_or_subfolder
from . import ir_action
from . import ir_ui_view
diff --git a/awesome_gallery/models/ir_action.py b/awesome_gallery/models/ir_action.py
index eae20acbf5..271875dd9d 100644
--- a/awesome_gallery/models/ir_action.py
+++ b/awesome_gallery/models/ir_action.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
from odoo import fields, models
@@ -7,4 +7,4 @@ class ActWindowView(models.Model):
view_mode = fields.Selection(selection_add=[
('gallery', "Awesome Gallery")
- ], ondelete={'gallery': 'cascade'})
+ ], ondelete={'gallery': 'cascade'})
diff --git a/awesome_gallery/models/ir_ui_view.py b/awesome_gallery/models/ir_ui_view.py
index 0c11b8298a..c6b454e432 100644
--- a/awesome_gallery/models/ir_ui_view.py
+++ b/awesome_gallery/models/ir_ui_view.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
from odoo import fields, models
diff --git a/awesome_kanban/__init__.py b/awesome_kanban/__init__.py
index 40a96afc6f..e69de29bb2 100644
--- a/awesome_kanban/__init__.py
+++ b/awesome_kanban/__init__.py
@@ -1 +0,0 @@
-# -*- coding: utf-8 -*-
diff --git a/awesome_kanban/__manifest__.py b/awesome_kanban/__manifest__.py
index 752fc12f78..387848fa6f 100644
--- a/awesome_kanban/__manifest__.py
+++ b/awesome_kanban/__manifest__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
{
'name': "Awesome Kanban",
'summary': """
diff --git a/awesome_owl/__init__.py b/awesome_owl/__init__.py
index 457bae27e1..24dfd571b5 100644
--- a/awesome_owl/__init__.py
+++ b/awesome_owl/__init__.py
@@ -1,3 +1,3 @@
-# -*- coding: utf-8 -*-
-from . import controllers
\ No newline at end of file
+
+from . import controllers
diff --git a/awesome_owl/__manifest__.py b/awesome_owl/__manifest__.py
index 98c9595fb7..0b4c08415a 100644
--- a/awesome_owl/__manifest__.py
+++ b/awesome_owl/__manifest__.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+
{
'name': "Awesome Owl",
@@ -36,7 +36,7 @@
# required for fa icons
'web/static/src/libs/fontawesome/css/font-awesome.css',
-
+
# include base files from framework
('include', 'web._assets_core'),
diff --git a/awesome_owl/controllers/__init__.py b/awesome_owl/controllers/__init__.py
index 457bae27e1..24dfd571b5 100644
--- a/awesome_owl/controllers/__init__.py
+++ b/awesome_owl/controllers/__init__.py
@@ -1,3 +1,3 @@
-# -*- coding: utf-8 -*-
-from . import controllers
\ No newline at end of file
+
+from . import controllers
diff --git a/awesome_owl/controllers/controllers.py b/awesome_owl/controllers/controllers.py
index bccfd6fe28..a50010bfca 100644
--- a/awesome_owl/controllers/controllers.py
+++ b/awesome_owl/controllers/controllers.py
@@ -1,5 +1,6 @@
from odoo import http
-from odoo.http import request, route
+from odoo.http import request
+
class OwlPlayground(http.Controller):
@http.route(['/awesome_owl'], type='http', auth='public')
diff --git a/estate/__init__.py b/estate/__init__.py
new file mode 100644
index 0000000000..0650744f6b
--- /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 0000000000..a810c6bf4c
--- /dev/null
+++ b/estate/__manifest__.py
@@ -0,0 +1,18 @@
+{
+ 'name': 'Estate',
+ 'description': 'A module to manage real estate advertisments',
+ 'depends': ['base'],
+
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'views/estate_property_views.xml',
+ 'views/estate_property_type_views.xml',
+ 'views/estate_property_tag_views.xml',
+ 'views/estate_menus.xml',
+ 'views/estate_property_offer_view.xml'
+ ],
+
+ 'installable': True,
+ 'application': True,
+ 'license': 'AGPL-3',
+}
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
new file mode 100644
index 0000000000..2f1821a39c
--- /dev/null
+++ b/estate/models/__init__.py
@@ -0,0 +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
new file mode 100644
index 0000000000..5982a68e79
--- /dev/null
+++ b/estate/models/estate_property.py
@@ -0,0 +1,102 @@
+from odoo import api, fields, models
+from odoo.exceptions import UserError
+
+
+class EstateProperty(models.Model):
+ _name = 'estate.property'
+ _description = 'The Real Estate Advertisement module'
+ _order = "id desc"
+ _sql_constraints = [
+ ('check_expected_price', 'CHECK(expected_price > 0)',
+ 'A property expected price must be strictly positive'),
+ ('check_selling_price', 'CHECK(selling_price >= 0)',
+ 'A property selling price must be positive'),
+ ]
+
+ name = fields.Char('Name', required=True)
+ description = fields.Text('Description')
+ postcode = fields.Char('Postcode')
+ bedrooms = fields.Integer('Bedrooms', default=2)
+ living_area = fields.Integer('Living Area (sqrm)')
+ facades = fields.Integer('Fcades')
+ garage = fields.Boolean(string="Garage", default=False)
+ garden = fields.Boolean(string="Garden", default=False)
+ garden_area = fields.Integer('Garden Area', default=0)
+ expected_price = fields.Float('Expected Price', required=True)
+ active = fields.Boolean(default=True)
+ sales_man_id = fields.Many2one("res.users", string="Salesmam", default=lambda self: self._uid)
+ buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
+ type_id = fields.Many2one("estate.property.type", string="Type")
+ tag_ids = fields.Many2many("estate.property.tag", string="Tags")
+ offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offer")
+ total_area = fields.Integer(compute="_compute_total_area", string="Total Area (sqrm)")
+ best_price = fields.Float(compute="_compute_best_price", string="Best Offer")
+ date_availability = fields.Date(
+ 'Availability Date',
+ copy=False,
+ default=fields.Datetime.add(fields.Datetime.today(), months=3),
+ )
+
+ selling_price = fields.Float(
+ 'Selling Price',
+ readonly=True,
+ copy=False,
+ )
+
+ garden_orientation = fields.Selection(
+ [
+ ('north', 'North'),
+ ('south', 'South'),
+ ('east', 'East'),
+ ('west', 'West'),
+ ],
+ string='Garden Orientation',
+ )
+ state = fields.Selection(
+ [
+ ("new", "New"),
+ ("offer_received", "Offer Received"),
+ ("offer_accepted", "Offer Accepted"),
+ ("sold", "Sold"),
+ ("canceled", "Canceled"),
+ ],
+ default="new",
+ required=True,
+ copy=False,
+ string="Status",
+ )
+
+ @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")
+ def _compute_best_price(self):
+ for record in self:
+ record.best_price = max(record.offer_ids.mapped('price'), default=0)
+
+ @api.onchange("garden")
+ def _onchange_garden(self):
+ if self.garden:
+ self.garden_area = 10
+ self.garden_orientation = "north"
+ else:
+ self.garden_area = 0
+ self.garden_orientation = False
+
+ def action_sold_property(self):
+ if "canceled" in self.mapped("state"):
+ raise UserError("Canceled properties cannot be sold.")
+ return self.write({"state": "sold", "active": False})
+
+ def action_cancel_property(self):
+ if "sold" in self.mapped("state"):
+ raise UserError("Sold properties cannot be canceled.")
+ return self.write({"state": "canceled", "active": False})
+
+ def unlink(self):
+ for record in self:
+ if not (record.state in ('new', 'canceled')):
+ raise UserError("It's not possible to delete a property which is not new or canceled!")
+ return super().unlink()
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
new file mode 100644
index 0000000000..aaca273817
--- /dev/null
+++ b/estate/models/estate_property_offer.py
@@ -0,0 +1,75 @@
+from odoo import api, fields, models
+from odoo.exceptions import UserError, ValidationError
+
+
+class estatePropertyOffer(models.Model):
+ _name = "estate.property.offer"
+ _description = "The offer of estate property"
+ _order = "price desc"
+ _sql_constraints = [
+ ('check_offer_price', 'CHECK(price > 0)',
+ 'A property offer price must be strictly positive'),
+ ]
+
+ price = fields.Float("Price")
+ partner_id = fields.Many2one("res.partner", required=True)
+ validity = fields.Integer(string="Validity (days)", default=7)
+ property_id = fields.Many2one('estate.property', required=True)
+ property_type_id = fields.Many2one("estate.property.type", related="property_id.type_id", store=True)
+
+ state = fields.Selection(
+ selection=[
+ ("accepted", "Accepted"),
+ ("refused", "Refused"),
+ ],
+ string="Status",
+ copy=False,
+ default=False,
+ )
+ date_deadline = fields.Date(
+ string="Deadline (days)",
+ compute="_compute_date_deadline",
+ inverse="_inverse_date_deadline",
+ )
+
+ @api.depends('validity')
+ def _compute_date_deadline(self):
+ for record in self:
+ if record.create_date:
+ record.date_deadline = fields.Datetime.add(record.create_date, days=record.validity)
+ else:
+ record.date_deadline = fields.Datetime.add(fields.Datetime.today(), days=record.validity)
+
+ def _inverse_date_deadline(self):
+ for offer in self:
+ date = offer.create_date.date() if offer.create_date else fields.Date.today()
+ offer.validity = (offer.date_deadline - date).days
+
+ def action_accepted(self):
+ if "Accepted" in self.mapped("state"):
+ raise UserError("An offer as already been accepted.")
+ self.write(
+ {
+ "state": "accepted",
+ }
+ )
+ return self.mapped("property_id").write(
+ {
+ "state": "offer_accepted",
+ "selling_price": self.price,
+ "buyer_id": self.partner_id.id,
+ }
+ )
+
+ def action_refused(self):
+ return self.write(
+ {
+ "state": "refused",
+ }
+ )
+
+ @api.constrains("price", "property_id.expected_price")
+ def check_price(self):
+ for record in self:
+ if record.price < (0.9 * record.property_id.expected_price):
+ raise ValidationError("The selling price must be at least 90%% of the expected price")
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
new file mode 100644
index 0000000000..696ab93314
--- /dev/null
+++ b/estate/models/estate_property_tag.py
@@ -0,0 +1,14 @@
+from odoo import fields, models
+
+
+class estatePropertyTag(models.Model):
+ _name = "estate.property.tag"
+ _description = "This module introduces property tags to the real estate model, allowing properties to be categorized with multiple descriptive tags like 'cozy' or 'renovated' using a many-to-many relationship."
+ _order = "name"
+ _sql_constraints = [
+ ('check_tag', 'UNIQUE(name)',
+ 'A property tag name must be unique'),
+ ]
+
+ name = fields.Char("Name", required=True)
+ color = fields.Integer("Color")
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
new file mode 100644
index 0000000000..eb88fad401
--- /dev/null
+++ b/estate/models/estate_property_type.py
@@ -0,0 +1,22 @@
+from odoo import api, fields, models
+
+
+class estatePropertyType(models.Model):
+ _name = "estate.property.type"
+ _description = "the type of the property ..."
+ _order = "sequence, name"
+ _sql_constraints = [
+ ('check_tag', 'UNIQUE(name)',
+ 'A property type name must be unique'),
+ ]
+
+ name = fields.Char("Name")
+ property_ids = fields.One2many("estate.property", "type_id", string="Properties")
+ sequence = fields.Integer("Secuence", default=1)
+ offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="offers")
+ offer_count = fields.Integer(string="Offers Count", compute="_compute_offers")
+
+ @api.depends("offer_ids")
+ def _compute_offers(self):
+ for record in self:
+ record.offer_count = len(record.offer_ids)
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv
new file mode 100644
index 0000000000..08ba2fb083
--- /dev/null
+++ b/estate/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+estate_properry_model,access_model,model_estate_property,base.group_user,1,1,1,1
+estate_properry_type_model,access_model,model_estate_property_type,base.group_user,1,1,1,1
+estate_properry_tag_model,access_model,model_estate_property_tag,base.group_user,1,1,1,1
+estate_properry_offer_model,access_model,model_estate_property_offer,base.group_user,1,1,1,1
diff --git a/estate/views/estate_menus.xml b/estate/views/estate_menus.xml
new file mode 100644
index 0000000000..8b296912b0
--- /dev/null
+++ b/estate/views/estate_menus.xml
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_offer_view.xml b/estate/views/estate_property_offer_view.xml
new file mode 100644
index 0000000000..b52343a6b9
--- /dev/null
+++ b/estate/views/estate_property_offer_view.xml
@@ -0,0 +1,40 @@
+
+
+
+ Offers
+ estate.property.offer
+ tree,form
+ [('property_type_id', '=', 'active_id')]
+
+
+
+ estate.property.offer.form
+ estate.property.offer
+
+
+
+
+
+
+ estate.property.offer.tree
+ estate.property.offer
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_tag_views.xml b/estate/views/estate_property_tag_views.xml
new file mode 100644
index 0000000000..d7ab432c23
--- /dev/null
+++ b/estate/views/estate_property_tag_views.xml
@@ -0,0 +1,33 @@
+
+
+
+ Property Tags
+ estate.property.tag
+ tree,form
+
+
+
+ estate.property.tag.tree
+ 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 0000000000..bdd7fd4e98
--- /dev/null
+++ b/estate/views/estate_property_type_views.xml
@@ -0,0 +1,64 @@
+
+
+
+ Property Types
+ estate.property.type
+ tree,form
+
+
+
+
+ estate.property.type.tree
+ estate.property.type
+
+
+
+
+
+
+
+
+
+ estate.property.type.form
+ estate.property.type
+
+
+
+
+
+
+ estate.property.type.search
+ estate.property.type
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
new file mode 100644
index 0000000000..02aa9a1c9b
--- /dev/null
+++ b/estate/views/estate_property_views.xml
@@ -0,0 +1,117 @@
+
+
+
+ property
+ estate.property
+ tree,form
+ {'search_default_available': 1}
+
+
+
+
+ estate_property_tree
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate_property_form
+ estate.property
+
+
+
+
+
+
+ estate.property.search
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+