- hello world
+
+
+
+
+
+
-
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..be6e30605f5
--- /dev/null
+++ b/estate/__manifest__.py
@@ -0,0 +1,21 @@
+{
+ 'name': "estate",
+ 'description': "test",
+ 'depends': [
+ 'base_setup'
+ ],
+ 'category': "Tutorials",
+ 'installable': True,
+ 'application': True,
+ 'data': [
+ 'views/estate_property_views.xml',
+ 'views/estate_property_offer_views.xml',
+ 'views/estate_property_type_views.xml',
+ 'views/estate_property_tag_views.xml',
+ 'views/estate_menu_views.xml',
+ 'views/res_user_views.xml',
+ 'security/ir.model.access.csv'],
+ 'author': 'Odoo S.A.',
+ 'license': 'LGPL-3'
+
+}
diff --git a/estate/i18n/estate.pot b/estate/i18n/estate.pot
new file mode 100644
index 00000000000..4e95f72c03b
--- /dev/null
+++ b/estate/i18n/estate.pot
@@ -0,0 +1,500 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# * estate
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 19.0+e\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-12-18 09:47+0000\n"
+"PO-Revision-Date: 2025-12-18 09:47+0000\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_type_model_form
+msgid ""
+"
\n"
+" Offers\n"
+" "
+msgstr ""
+
+#. module: estate
+#: model:ir.model.constraint,message:estate.constraint_estate_property_offer_postif_price
+#: model:ir.model.constraint,message:estate.constraint_estate_property_positif_expected_price
+#: model:ir.model.constraint,message:estate.constraint_estate_property_positif_selling_price
+msgid "A price can't be negatif"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Accept"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property_offer__status__accepted
+msgid "Accepted"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__active
+msgid "Active"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_search
+msgid "Available"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__date_availability
+msgid "Available From"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__best_price
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_kanban
+msgid "Best Price"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__buyer_id
+msgid "Buyer"
+msgstr ""
+
+#. module: estate
+#. odoo-python
+#: code:addons/estate/models/estate_property.py:0
+msgid "Can't cancel sold property."
+msgstr ""
+
+#. module: estate
+#. odoo-python
+#: code:addons/estate/models/estate_property.py:0
+msgid "Can't delete non-new and non-cancelled property"
+msgstr ""
+
+#. module: estate
+#. odoo-python
+#: code:addons/estate/models/estate_property.py:0
+msgid "Can't sell cancelled property."
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_form
+msgid "Cancel"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__state__cancelled
+msgid "Cancelled"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__color
+msgid "Color"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__garage
+msgid "Contains a Garage ?"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__garden
+msgid "Contains a Garden ?"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__create_uid
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__create_uid
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__create_uid
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__create_uid
+msgid "Created by"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__create_date
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__create_date
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__create_date
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__create_date
+msgid "Created on"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__date_deadline
+msgid "Deadline"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_form
+msgid "Description"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__display_name
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__display_name
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__display_name
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__display_name
+#: model:ir.model.fields,field_description:estate.field_res_users__display_name
+msgid "Display Name"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__garden_orientation__east
+msgid "East"
+msgstr ""
+
+#. module: estate
+#: model:ir.ui.menu,name:estate.estate_menu_root
+msgid "Estate"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__expected_price
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_kanban
+msgid "Expected Price"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__garden_area
+msgid "Garden Area Size m^2"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__garden_orientation
+msgid "Garden orientation"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__id
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__id
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__id
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__id
+#: model:ir.model.fields,field_description:estate.field_res_users__id
+msgid "ID"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__last_seen
+msgid "Last Seen"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__write_uid
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__write_uid
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__write_uid
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__write_uid
+msgid "Last Updated by"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__write_date
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__write_date
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__write_date
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__write_date
+msgid "Last Updated on"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__living_area
+msgid "Living Area Size m^2"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__name
+#: model:ir.model.fields,field_description:estate.field_estate_property_tag__name
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__name
+msgid "Name"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__state__new
+msgid "New"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__garden_orientation__north
+msgid "North"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__facades
+msgid "Number of Facades"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__offer_count
+msgid "Number of Offers"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__bedrooms
+msgid "Number of bedrooms"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__offer_ids
+msgid "Offer"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__state__offer_accepted
+msgid "Offer Accepted"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__state__offer_received
+msgid "Offer Received"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Offer Sender"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Offer deadline"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Offer validity time (days)"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__offer_ids
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_form
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_type_model_form
+msgid "Offers"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_form
+msgid "Other Info"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__partner_id
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_model_form
+msgid "Partner"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__postcode
+msgid "Postcode"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_search
+msgid "Postcodes"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__price
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_model_form
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Price"
+msgstr ""
+
+#. module: estate
+#: model:ir.actions.act_window,name:estate.estate_property_model_action
+#: model:ir.model,name:estate.model_estate_property
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__property_id
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__property_ids
+#: model:ir.model.fields,field_description:estate.field_res_users__property_ids
+#: model:ir.ui.menu,name:estate.estate_property_menu
+#: model:ir.ui.menu,name:estate.estate_property_menu_action
+msgid "Property"
+msgstr ""
+
+#. module: estate
+#: model:ir.actions.act_window,name:estate.estate_property_offer_model_action
+#: model:ir.model,name:estate.model_estate_property_offer
+msgid "Property Offer"
+msgstr ""
+
+#. module: estate
+#: model:ir.actions.act_window,name:estate.estate_property_tag_model_action
+#: model:ir.model,name:estate.model_estate_property_tag
+#: model:ir.ui.menu,name:estate.estate_property_tag_menu_acion
+msgid "Property Tag"
+msgstr ""
+
+#. module: estate
+#: model:ir.actions.act_window,name:estate.estate_property_type_model_action
+#: model:ir.model,name:estate.model_estate_property_type
+#: model:ir.ui.menu,name:estate.estate_property_type_menu_acion
+msgid "Property Type"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_form
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_tag_model_form
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_view_list
+msgid "Property list"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_model_form
+msgid "Property offer form"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Property offer list"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_search
+msgid "Property search"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_tag_model_list
+msgid "Property tag list"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Property type"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_type_model_form
+msgid "Property type Form"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_type_model_list
+msgid "Property type list"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.constraint,message:estate.constraint_estate_property_type_unique_type
+msgid "Property type name must be unique in database"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.res_users_view_form
+msgid "Real Estate Properties"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_offer_view_list
+msgid "Refuse"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property_offer__status__refused
+msgid "Refused"
+msgstr ""
+
+#. module: estate
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_form
+msgid "Sell"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__seller_id
+msgid "Seller"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__selling_price
+#: model_terms:ir.ui.view,arch_db:estate.estate_property_model_kanban
+msgid "Selling Price"
+msgstr ""
+
+#. module: estate
+#. odoo-python
+#: code:addons/estate/models/estate_property.py:0
+msgid "Selling price is too low %(price)s"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_type__sequence
+msgid "Sequence"
+msgstr ""
+
+#. module: estate
+#: model:ir.ui.menu,name:estate.estate_settings_menu
+msgid "Settings"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__state__sold
+msgid "Sold"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__garden_orientation__south
+msgid "South"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__state
+msgid "State"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__status
+msgid "Status"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.constraint,message:estate.constraint_estate_property_tag_unique_tag
+msgid "Tag name must be unique in database"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__tag_ids
+msgid "Tags"
+msgstr ""
+
+#. module: estate
+#. odoo-python
+#: code:addons/estate/models/estate_property_offer.py:0
+msgid "This offer is lower than what has already been offered."
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__total_area
+msgid "Total Area m^2"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__property_type_id
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__property_type_id
+msgid "Type"
+msgstr ""
+
+#. module: estate
+#: model:ir.model,name:estate.model_res_users
+msgid "User"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property_offer__validity
+msgid "Validity Duration"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields.selection,name:estate.selection__estate_property__garden_orientation__west
+msgid "West"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,field_description:estate.field_estate_property__description
+msgid "description"
+msgstr ""
+
+#. module: estate
+#: model:ir.model.fields,help:estate.field_estate_property_type__sequence
+msgid "use to order the list inside the type view"
+msgstr ""
diff --git a/estate/models/__init__.py b/estate/models/__init__.py
new file mode 100644
index 00000000000..8f914bbb526
--- /dev/null
+++ b/estate/models/__init__.py
@@ -0,0 +1,5 @@
+from . import estate_property
+from . import estate_property_type
+from . import estate_property_tag
+from . import estate_property_offer
+from . import res_user
diff --git a/estate/models/estate_property.py b/estate/models/estate_property.py
new file mode 100644
index 00000000000..6515699f95e
--- /dev/null
+++ b/estate/models/estate_property.py
@@ -0,0 +1,100 @@
+from dateutil.relativedelta import relativedelta
+from odoo import fields, models, api
+from odoo.tools.float_utils import float_compare, float_is_zero
+from odoo.exceptions import ValidationError, UserError
+
+
+class EstateProperty(models.Model):
+ _name = "estate.property"
+ _description = "Property"
+ _order = "id desc"
+
+ _positif_expected_price = models.Constraint("CHECK (expected_price >= 0)", "A price can't be negatif")
+ _positif_selling_price = models.Constraint("CHECK (selling_price >= 0)", "A price can't be negatif")
+
+ state = fields.Selection(selection=[
+ ("new", "New"),
+ ("offer_received", "Offer Received"),
+ ("offer_accepted", "Offer Accepted"),
+ ("sold", "Sold"),
+ ("cancelled", "Cancelled")
+ ], default='new')
+
+ active = fields.Boolean('Active', default=True)
+ name = fields.Char(required=True, default="Unknown", string="Name")
+ description = fields.Text(string="description")
+ postcode = fields.Char(string="Postcode")
+ date_availability = fields.Date(string="Available From", copy=False, default=lambda self: fields.Datetime.today() + relativedelta(months=3))
+ last_seen = fields.Date(string="Last Seen", default=lambda self: fields.Datetime.now())
+ expected_price = fields.Float(required=True, string="Expected Price")
+ selling_price = fields.Float(readonly=True, copy=False, string="Selling Price")
+ best_price = fields.Float(string="Best Price", compute="_compute_best_price")
+ bedrooms = fields.Integer(default=2, string="Number of bedrooms")
+ living_area = fields.Integer(string="Living Area Size m^2")
+ facades = fields.Integer(string="Number of Facades")
+ garage = fields.Boolean(string="Contains a Garage ?")
+ garden = fields.Boolean(string="Contains a Garden ?")
+ garden_area = fields.Integer(string="Garden Area Size m^2")
+ garden_orientation = fields.Selection(string="Garden orientation", selection=[
+ ('north', 'North'),
+ ('south', 'South'),
+ ('east', 'East'),
+ ('west', 'West')])
+
+ total_area = fields.Float(string="Total Area m^2", compute="_compute_total_area")
+ property_type_id = fields.Many2one("estate.property.type", string="Type")
+ buyer_id = fields.Many2one("res.partner", string="Buyer")
+ seller_id = fields.Many2one("res.users", default=lambda self: self.env.user, string="Seller")
+ tag_ids = fields.Many2many("estate.property.tag", string="Tags")
+ offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers")
+
+ def sell_property(self):
+ for record in self:
+ if (record.state == "cancelled"):
+ raise UserError(self.env._("Can't sell cancelled property."))
+ record.state = "sold"
+ return True
+
+ def cancel_property(self):
+ for record in self:
+ if (record.state == "sold"):
+ raise UserError(self.env._("Can't cancel sold property."))
+ record.state = "cancelled"
+ return True
+
+ @api.depends('living_area', 'garden_area')
+ def _compute_total_area(self):
+ for record in self:
+ record.total_area = record.garden_area + record.living_area
+
+ @api.depends('offer_ids')
+ def _compute_best_price(self):
+ for record in self:
+ if (not record.offer_ids):
+ record.best_price = 0
+ continue
+ record.best_price = max(record.offer_ids.mapped('price'))
+
+ @api.onchange("offer_ids")
+ def _on_change_offer_ids(self):
+ if (self.state == 'new' and len(self.offer_ids) != 0):
+ self.state = 'offer_received'
+
+ @api.onchange("garden")
+ def _on_change_garden(self):
+ self.garden_area = 10 if self.garden else 0
+ self.garden_orientation = 'north' if self.garden else ''
+
+ @api.constrains('selling_price', 'expected_price')
+ def _constrain_prices(self):
+ for record in self:
+ if float_is_zero(record.selling_price, 2):
+ continue
+ if (float_compare(record.selling_price, record.expected_price * 0.8, 2) == -1):
+ raise ValidationError(self.env._("Selling price is too low %(price)s", price=record.selling_price))
+
+ @api.ondelete(at_uninstall=False)
+ def _unlink_excpet_cancel_new(self):
+ for record in self:
+ if (record.state != 'new' and record.state != 'cancelled'):
+ raise UserError(self.env._("Can't delete non-new and non-cancelled property"))
diff --git a/estate/models/estate_property_offer.py b/estate/models/estate_property_offer.py
new file mode 100644
index 00000000000..2725617a495
--- /dev/null
+++ b/estate/models/estate_property_offer.py
@@ -0,0 +1,60 @@
+from datetime import datetime, time
+from dateutil.relativedelta import relativedelta
+from odoo import fields, models, api
+from odoo.exceptions import UserError
+from odoo.tools.float_utils import float_compare
+
+
+class EstatePropertyOffer(models.Model):
+ _name = "estate.property.offer"
+ _description = "Property Offer"
+ _order = "price desc"
+
+ _postif_price = models.Constraint("CHECK (price > 0)", "A price can't be negatif")
+
+ price = fields.Float(string="Price")
+ status = fields.Selection(copy=False, selection=[
+ ("accepted", "Accepted"),
+ ("refused", "Refused")])
+
+ partner_id = fields.Many2one('res.partner', required=True)
+ property_id = fields.Many2one('estate.property', ondelete='cascade', required=True)
+ property_type_id = fields.Many2one(related="property_id.property_type_id", store=True)
+ validity = fields.Integer(string="Validity Duration", default=7)
+ date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline")
+
+ def accept_offer(self):
+ for record in self:
+ if (record.property_id.selling_price == 0):
+ record.status = "accepted"
+ record.property_id.buyer_id = record.partner_id
+ record.property_id.selling_price = record.price
+ record.property_id.state = 'offer_accepted'
+ return True
+
+ def refused_offer(self):
+ for record in self:
+ record.status = "refused"
+ return True
+
+ @api.depends("validity")
+ def _compute_date_deadline(self):
+ for record in self:
+ if (isinstance(record.create_date, bool)):
+ record.date_deadline = fields.Datetime.now() + relativedelta(days=record.validity)
+ return
+ record.date_deadline = record.create_date + relativedelta(days=record.validity)
+
+ def _inverse_date_deadline(self):
+ for record in self:
+ record.validity = (datetime.combine(record.date_deadline, time()) - record.create_date).days
+
+ @api.model
+ def create(self, vals_list):
+ property_ids = (val['property_id'] for val in vals_list)
+ property = self.env['estate.property'].browse(property_ids)
+
+ for i, record in enumerate(vals_list):
+ if (float_compare(record['price'], property[i].best_price, 2) == -1):
+ raise UserError(self.env._("This offer is lower than what has already been offered."))
+ return super().create(vals_list)
diff --git a/estate/models/estate_property_tag.py b/estate/models/estate_property_tag.py
new file mode 100644
index 00000000000..93b5a4f5606
--- /dev/null
+++ b/estate/models/estate_property_tag.py
@@ -0,0 +1,12 @@
+from odoo import fields, models
+
+
+class EstatePropertyTag(models.Model):
+ _name = "estate.property.tag"
+ _description = "Property Tag"
+ _order = "name asc"
+
+ _unique_tag = models.UniqueIndex("(name)", "Tag name must be unique in database")
+
+ name = fields.Char(string="Name", required=True)
+ color = fields.Integer()
diff --git a/estate/models/estate_property_type.py b/estate/models/estate_property_type.py
new file mode 100644
index 00000000000..5b25ae43da9
--- /dev/null
+++ b/estate/models/estate_property_type.py
@@ -0,0 +1,20 @@
+from odoo import fields, models, api
+
+
+class EstatePropertyType(models.Model):
+ _name = "estate.property.type"
+ _description = "Property Type"
+ _order = "sequence, name asc"
+
+ _unique_type = models.UniqueIndex("(name)", "Property type name must be unique in database")
+
+ name = fields.Char(string="Name", required=True)
+ property_ids = fields.One2many("estate.property", "property_type_id")
+ sequence = fields.Integer('Sequence', default=1, help="use to order the list inside the type view")
+ offer_ids = fields.One2many('estate.property.offer', 'property_type_id')
+ offer_count = fields.Integer(string="Number of Offers", compute='_compute_offer_count')
+
+ @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/res_user.py b/estate/models/res_user.py
new file mode 100644
index 00000000000..718699c6cb4
--- /dev/null
+++ b/estate/models/res_user.py
@@ -0,0 +1,6 @@
+from odoo import models, fields
+
+
+class ResUsers(models.Model):
+ _inherit = 'res.users'
+ property_ids = fields.One2many("estate.property", "seller_id", domain="['|',('state','=','Offer_Received'),('state','=','New')]")
diff --git a/estate/security/ir.model.access.csv b/estate/security/ir.model.access.csv
new file mode 100644
index 00000000000..4073dc03078
--- /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
+access_estate_property_model,access_estate_property_model,model_estate_property,base.group_user,1,1,1,1
+access_estate_property_type_model,access_estate_property_type_model,model_estate_property_type,base.group_user,1,1,1,1
+access_estate_property_tag_model,access_estate_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1
+access_estate_property_offer_model,access_estate_property_offer_model,model_estate_property_offer,base.group_user,1,1,1,1
diff --git a/estate/views/estate_menu_views.xml b/estate/views/estate_menu_views.xml
new file mode 100644
index 00000000000..20051185c6e
--- /dev/null
+++ b/estate/views/estate_menu_views.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/estate/views/estate_property_offer_views.xml b/estate/views/estate_property_offer_views.xml
new file mode 100644
index 00000000000..f0f32300bcc
--- /dev/null
+++ b/estate/views/estate_property_offer_views.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ Property Offer
+ estate.property.offer
+ list,form
+ [('property_type_id', '=', active_id)]
+
+
+
+ estate.property.offer.form
+ estate.property.offer
+
+
+
+
+
+
+ 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..382c51119a1
--- /dev/null
+++ b/estate/views/estate_property_tag_views.xml
@@ -0,0 +1,21 @@
+
+
+
+
+ Property Tag
+ estate.property.tag
+ list
+
+
+
+ estate.property.tag.list
+ estate.property.tag
+
+
+
+
+
+
+
+
+
diff --git a/estate/views/estate_property_type_views.xml b/estate/views/estate_property_type_views.xml
new file mode 100644
index 00000000000..07f299ab97c
--- /dev/null
+++ b/estate/views/estate_property_type_views.xml
@@ -0,0 +1,56 @@
+
+
+
+
+ Property Type
+ estate.property.type
+ list,form
+
+
+
+ estate.property.type.form
+ estate.property.type
+
+
+
+
+
+
+ estate.property.type.list
+ estate.property.type
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate/views/estate_property_views.xml b/estate/views/estate_property_views.xml
new file mode 100644
index 00000000000..740575c80fe
--- /dev/null
+++ b/estate/views/estate_property_views.xml
@@ -0,0 +1,131 @@
+
+
+
+
+ Property
+ estate.property
+ list,form,kanban
+ {'search_default_available':True}
+
+
+
+ estate.property.list
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate.property.form
+ estate.property
+
+
+
+
+
+
+ estate.property.search
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ estate.property.kanban
+ estate.property
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/estate/views/res_user_views.xml b/estate/views/res_user_views.xml
new file mode 100644
index 00000000000..aa4b825a8c5
--- /dev/null
+++ b/estate/views/res_user_views.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ res.users.view.form.inherit.estate
+ res.users
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/estate_account/__init__.py b/estate_account/__init__.py
new file mode 100644
index 00000000000..0650744f6bc
--- /dev/null
+++ b/estate_account/__init__.py
@@ -0,0 +1 @@
+from . import models
diff --git a/estate_account/__manifest__.py b/estate_account/__manifest__.py
new file mode 100644
index 00000000000..341b8a7db19
--- /dev/null
+++ b/estate_account/__manifest__.py
@@ -0,0 +1,14 @@
+{
+ 'name': "estate_account",
+ 'description': "link the estate module to the invoiceing module",
+ 'depends': [
+ 'base_setup', 'estate', 'account'
+ ],
+ 'category': "Tutorials",
+ 'installable': True,
+ 'application': False,
+ 'data': [],
+ 'author': 'Odoo S.A.',
+ 'license': 'LGPL-3'
+
+}
diff --git a/estate_account/models/__init__.py b/estate_account/models/__init__.py
new file mode 100644
index 00000000000..5e1963c9d2f
--- /dev/null
+++ b/estate_account/models/__init__.py
@@ -0,0 +1 @@
+from . import estate_property
diff --git a/estate_account/models/estate_property.py b/estate_account/models/estate_property.py
new file mode 100644
index 00000000000..b37e980a120
--- /dev/null
+++ b/estate_account/models/estate_property.py
@@ -0,0 +1,27 @@
+from odoo import models, Command
+
+
+class EstateProperty(models.Model):
+ _inherit = "estate.property"
+
+ def sell_property(self):
+ for record in self:
+ invoice_val = {
+ 'state': 'draft',
+ 'move_type': 'out_invoice',
+ 'partner_id': int(record.buyer_id),
+ 'line_ids': [
+ Command.create({
+ "name": "First Payment",
+ "quantity": 1,
+ "price_unit": record.selling_price * 0.06
+ }),
+ Command.create({
+ "name": "Administrative Fees",
+ "quantity": 1,
+ "price_unit": 100.00
+ })
+ ]
+ }
+ self.env['account.move'].sudo().with_context(default_move_type='out_invoice').create(invoice_val)
+ return super().sell_property()