Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
22 changes: 22 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
'name': "Real Estate",
'application': True,
'installable': True,
'category': "Real Estate/Brokerage",
'version': '1.0',
'summary': "The Real Estate Advertisement",
"depends": [
"base",
],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/res_users_views.xml',
'views/estate_menus.xml',
],
'author': "rencelotm",
'license': "AGPL-3"
}
5 changes: 5 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -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_users
218 changes: 218 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
import datetime
from odoo import api, fields, models, exceptions
from odoo.tools.float_utils import float_compare, float_is_zero

class EstateProperty(models.Model):
_name = "estate.estate.property"
_description = "Real Estate Property Module Tutorial"
_order = "id desc"

## SQL Constraints Section ##

_check_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'The expected price should be a positive number'
)
_check_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'The selling price should be a positive number'
)

name = fields.Char(
"Property Name",
required=True,
help="Enter the name of the property"
)
active = fields.Boolean(default=True)
description = fields.Text(
"Property Description",
help="Enter a quick description of the characteristic of the property"
)
postcode = fields.Char(
"Postcode",
help="Enter the postcode of the property"
)
date_availability = fields.Date(
"Date Availability",
copy=False,
help="Enter the date at which the property is available. By default set to 3 months",
default=lambda _: fields.Date.today() + datetime.timedelta(weeks=12) # Equivalent to 3 months
)
expected_price = fields.Float(
"Expected Price",
required=True,
help="The expected price for the property."
)
selling_price = fields.Float(
"Selling Price",
readonly=True,
copy=False,
default=0.0,
)
bedrooms = fields.Integer(
"Nb Bedrooms",
default=2,
help="The number of bedrooms that the property has. By default set to 2."
)
living_area = fields.Integer(
"Living Area",
help="The number of square meters the living area has."
)
facades = fields.Integer(
"Nb Facades",
help="The number of facades the property has. Cannot be more that four."
)
garage = fields.Boolean(
"Garage",
help="Is the property has a garage?"
)
garden = fields.Boolean(
"Garden",
help="Is the property has a garden?"
)
garden_area = fields.Integer(
"Nb Garden Area",
help="Enter the number of square meters the garden has. Only if the property has a garden"
)
garden_orientation = fields.Selection(
string="Orientation",
selection = [
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
help="Choose the orientation of the garden"
)
state = fields.Selection(
string="State",
selection = [
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
required = True,
copy = False,
default="new",
)
# Property Type ID
property_type_id = fields.Many2one(
"estate.property.type",
string="Property Type",
help="The type of the property (House, Loft, Apartment, etc.)"
)
# Buyer and Salesperson
salesperson = fields.Many2one(
"res.users",
string="Salesperson",
index=True,
default=lambda self: self.env.user,
help="Name of the salesperson"
) # Internal entity
buyer = fields.Many2one(
"res.partner",
string="Buyer",
index=True,
copy=False,
help="Name of the potential buyer for the property"
) # External entity

# Tags as Many2many
tags_ids = fields.Many2many(
"estate.property.tag",
string="Tags"
)

# Offers as One2many
offer_ids = fields.One2many(
"estate.property.offer",
"property_id",
string="Offers",
)
total_area = fields.Float(
compute="_compute_total_area",
string="Total Area (sqm)",
help="Total area of the property"
)
best_offer = fields.Float(
compute="_compute_best_offer",
string="Best Offer",
help="The best offer proposed so far"
)

## API Constraints Section ##

@api.constrains("selling_price", "expected_price")
def _check_selling_price(self):
for record in self:
if not float_is_zero(record.selling_price, 2) and float_compare(record.selling_price, record.expected_price * 0.9, 4) < 0 :
raise exceptions.ValidationError("The selling price cannot be less than 90% of the expected price!")

## Method Section ##

def sold_property_action(self):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quand la méthode n'as pas _ au début, elle est publique. Ça veut dire que tout le monde peut y accéder en dehors des vues qu'on crée. Du coup, il faut tjrs les protéger en vérifiant si l'utilisateur a le droit de les utiliser.

Voilà un exemple : https://github.com/odoo/odoo/blob/98e3020bcffaf449291d1e6664ba613761f37331/addons/event_crm/models/event_event.py#L27


if not self.env.user.has_group('estate.group_system'):
exceptions.UserError("You do not have permission to perform this action!")

for record in self:
if record.state != "cancelled":
record.state = "sold"
else:
raise exceptions.UserError("A cancelled property cannot be sold!")
return True

def cancel_property_action(self):
for record in self:
if record.state != "sold":
record.state = "cancelled"
else:
raise exceptions.UserError("A sold property cannot be cancelled!")
return True

## CRUD Methods ##
@api.ondelete(at_uninstall=True)
def property_delete_checker(self):
for record in self:
if record.state not in ['new', 'cancelled']:
raise exceptions.UserError("Cannot delete the property except if it's 'new' or 'cancelled' one!")
return True


@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_offer(self):
for record in self:
if record.offer_ids:
record.best_offer = max(record.offer_ids.mapped('price'))
else:
record.best_offer = 0.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 = ""

@api.onchange("offer_ids")
def _onchange_offers(self):
if self.offer_ids and self.state == "new":
self.state = "offer_received"
if len(self.offer_ids) == 0:
self.state = "new"

@api.onchange("state")
def _onchange_state(self):
if self.state == "cancelled" or self.state == "sold":
self.active = False
else:
self.active = True
83 changes: 83 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import datetime
from odoo import api, fields, models, exceptions


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = "Define an offer on an property"
_order = "price desc"
price = fields.Float()
status = fields.Selection(
[
('accepted', 'Accepted'),
('refused', 'Refused'),
],
copy=False,
readonly=True
)
validity = fields.Integer(default=7, string="Validity (days)")
date_deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline", string="Deadline")
partner_id = fields.Many2one("res.partner", required=True)
property_id = fields.Many2one("estate.estate.property", required=True)
property_state = fields.Selection(related="property_id.state", readonly=True)
property_type_ids = fields.Many2one(related="property_id.property_type_id", readonly=True, store=True)

## SQL Constraints Section ##

_check_price = models.Constraint(
'CHECK(price > 0)',
'The offer price cannot be less than 0'
)

## Methods Section ##

def accept_offer_action(self):
for record in self:
if record.property_id.state == "cancelled" or record.property_id.state == "sold":
raise exceptions.UserError("An offer on a sold or cancelled property cannot be accepted!")
if record.status:
raise exceptions.UserError("Cannot change the status of an already statued offer!")
for offer in record.property_id.offer_ids:
if offer.status == "accepted":
raise exceptions.UserError("Cannot have more than 1 accepted offer!")

record.status = "accepted"
record.property_id.buyer = record.partner_id
record.property_id.selling_price = record.price
record.property_id.state = "offer_accepted"
return True

def refuse_offer_action(self):
for record in self:
if record.status:
raise exceptions.UserError("Cannot change the status of an already statued offer!")
else:
record.status = "refused"
return True

@api.depends("create_date", "validity")
def _compute_deadline(self):
for record in self:
if not record.create_date:
record.date_deadline = fields.Date.today() + datetime.timedelta(days=record.validity)
else:
record.date_deadline = record.create_date + datetime.timedelta(days=record.validity)

@api.depends("create_date", "date_deadline")
def _inverse_deadline(self):
for record in self:
if record.create_date and record.date_deadline:
record.validity = (record.date_deadline - record.create_date.date()).days
else:
record.validity = (record.date_deadline - fields.Date.today()).days

## CRUD Method ##
@api.model
def create(self, vals):
existing_offers = self.search([("property_id", "=", vals[0]["property_id"])])
for offer in existing_offers:
if vals[0]["price"] <= offer.price:
raise exceptions.UserError("The offer price cannot be lower than price of another offer!")
property = self.env["estate.estate.property"].browse(vals[0]["property_id"])
property.state = "offer_received"
return super().create(vals)
16 changes: 16 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models

class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Tag for Estate Property"
_order = "name"

## SQL Constraints Section ##
_check_name = models.Constraint(
'UNIQUE(name)',
'A tag should be unique'
)

name = fields.Char(required=True)
property_ids = fields.One2many("estate.estate.property", "tags_ids")
color = fields.Integer()
26 changes: 26 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from odoo import api, fields, models

class PropertyType(models.Model):
_name = "estate.property.type"
_description = "Types of Property"
_order = "name"
name = fields.Char(required=True)
sequence = fields.Integer('Sequence', default=1)
property_ids = fields.One2many("estate.estate.property", "property_type_id")

# Stat button fields
offer_ids = fields.One2many("estate.property.offer", "property_type_ids")
offer_count = fields.Integer(compute="_compute_offer_count")

## Constraints Section ##
_check_name = models.Constraint(
'UNIQUE(name)',
'The type of a property should be unique'
)

## Computed fields ##
@api.depends("offer_ids")
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids)
return True
11 changes: 11 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields, models


class ResUsers(models.Model):
_inherit = "res.users"
property_ids = fields.One2many(
"estate.estate.property",
"salesperson",
string="Salesperson",
domain=[("state", "in", ["new", "offer_received"])]
)
Loading