Skip to content
Open
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
20 changes: 20 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "Real Estate",
"version": "0.1",
"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",
],
"installable": True,
"application": True,
"author": "Odoo S.A.",
"license": "LGPL-3",
}
7 changes: 7 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from . import (
estate_property,
estate_property_offer,
estate_property_tag,
estate_property_type,
res_users,
)
168 changes: 168 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
from odoo import api, fields, models
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare, float_is_zero


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

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
"Availability Date",
default=lambda self: fields.Date.add(fields.Date.today(), months=3),
copy=False,
)
expected_price = fields.Float(required=True)
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
selection=[
("n/a", "N/A"),
("north", "North"),
("east", "East"),
("south", "South"),
("west", "West"),
],
)
state = fields.Selection(
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("canceled", "Canceled"),
],
default="new",
copy=False,
)
active = fields.Boolean(default=True)

# Many2one references
type_id = fields.Many2one(comodel_name="estate.property.type")
Comment thread
Emivvvvv marked this conversation as resolved.
buyer_id = fields.Many2one(comodel_name="res.partner", copy=False)
salesperson_id = fields.Many2one(
comodel_name="res.users",
default=lambda self: self.env.user,
)

# One2many references
offer_ids = fields.One2many(
comodel_name="estate.property.offer",
inverse_name="property_id",
)

# Many2many references
tag_ids = fields.Many2many(
comodel_name="estate.property.tag",
string="Tags",
)

# Computed
total_area = fields.Float(compute="_compute_total_area")

@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")

@api.depends("offer_ids.price")
def _compute_best_price(self):
for record in self:
prices = record.offer_ids.mapped("price")
record.best_price = max(prices) if prices else 0.0

# On change
@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 = "n/a"

def action_property_sold(self):
self.ensure_one()
if self.state == "canceled":
raise UserError(
self.env._(
"This property has already been canceled. It can not be sold!"
)
)
elif self.state == "sold":
raise UserError(
self.env._(
"This property has already been sold. It can not be sold again!"
)
)
elif not self.offer_ids:
raise UserError(self.env._("There is no offer with this property."))
else:
self.state = "sold"

def action_property_canceled(self):
for record in self:
if record.state == "sold":
raise UserError(
self.env._(
"This property has already been sold. It can not be canceled!"
)
)
elif record.state == "canceled":
raise UserError(
self.env._(
"This property has already been canceled. It can not be canceled again!"
)
)
else:
record.state = "canceled"

# Constraints
_check_expected_price = models.Constraint(
"CHECK(expected_price >= 0)", "Expected price must be >= 0!"
)
_check_selling_price_positive = models.Constraint(
"CHECK(selling_price >= 0)", "Selling price must be >= 0!"
)

@api.constrains("selling_price", "expected_price")
def _check_selling_price(self):
for record in self:
if float_is_zero(record.selling_price, precision_digits=2):
continue

if (
not float_compare(
record.selling_price,
record.expected_price * 0.9,
precision_digits=2,
)
== 1
):
raise UserError(
self.env._(
"Selling price cannot be lower than 90% of the expected price!"
)
)

# Model decorators
@api.ondelete(at_uninstall=False)
def _prevent_deletion_if_not_new_or_canceled(self):
for record in self:
if record.state not in ("new", "canceled"):
raise UserError(
self.env._(
"You cannot delete a property unless its state is 'New' or 'Canceled'."
)
)
105 changes: 105 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
from datetime import timedelta

from odoo import api, fields, models
from odoo.exceptions import UserError


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer Model"
_order = "price desc"

price = fields.Float()
status = fields.Selection(
selection=[
("accepted", "Accepted"),
("refused", "Refused"),
],
copy=False,
)
validity = fields.Integer("Validity (days)", default=7)

# Many2one references
partner_id = fields.Many2one(comodel_name="res.partner", required=True)
property_id = fields.Many2one(comodel_name="estate.property", required=True)
property_type_id = fields.Many2one(
comodel_name="estate.property.type", related="property_id.type_id", store=True
)

# Computed fields
date_deadline = fields.Date(
string="Deadline Date",
compute="_compute_date_deadline",
inverse="_inverse_date_deadline",
)

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

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

def action_confirm_offer(self):
self.ensure_one()

# Check property have already been sold or canceled
if self.property_id.state in ["sold", "canceled"]:
raise UserError(
self.env._("This property has already been sold or canceled!")
)

# Any accepted offer?
if any(o.status == "accepted" for o in self.property_id.offer_ids):
raise UserError(self.env._("An offer have already been accepted!"))

# Accept offer
self.status = "accepted"
self.property_id.write(
{
"state": "offer_accepted",
"selling_price": self.price,
"buyer_id": self.partner_id.id,
}
)

def action_refuse_offer(self):
for offer in self:
offer.status = "refused"

_check_price = models.Constraint("CHECK(price >= 0)", "Offer price must be >= 0!")

# Model decorators
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
property_rec = self.env["estate.property"].browse(vals["property_id"])

if property_rec.state in ["sold", "canceled"]:
raise UserError(
self.env._(
"Can't create an offer for an already sold/canceled property!"
)
)

for offer in property_rec.offer_ids:
if vals.get("price", 0) <= offer.price:
raise UserError(
self.env._(
"The offer amount must be strictly higher than existing offers."
)
)

property_rec.state = "offer_received"

return super().create(vals_list)
13 changes: 13 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import fields, models


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

name = fields.Char("Tag Name", required=True)
color = fields.Integer(default=1)

# Constraints
_unique_name = models.Constraint("UNIQUE(name)", "Property Tag name must unique!")
31 changes: 31 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from odoo import fields, models, api


class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate Property Type Model"
_order = "name"

name = fields.Char("Type Name", required=True)
sequence = fields.Integer(default=1, help="Used to order stages. Lower is better.")

# One2many relations
property_ids = fields.One2many(
comodel_name="estate.property", inverse_name="type_id", string="Properties"
)
offer_ids = fields.One2many(
comodel_name="estate.property.offer",
inverse_name="property_type_id",
string="Offers",
)

# Computed
offer_count = fields.Integer(compute="_compute_offer_count")

@api.depends("offer_ids")
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids)

# Constraints
_unique_name = models.Constraint("UNIQUE(name)", "Property Type name must unique!")
12 changes: 12 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class ResUser(models.Model):
_inherit = "res.users"

property_ids = fields.One2many(
comodel_name="estate.property",
inverse_name="salesperson_id",
string="Real Estate Properties",
domain=[("state", "in", ["new", "offer_received"])],
)
5 changes: 5 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
estate.property,estate.property,model_estate_property,base.group_user,1,1,1,1
estate.property.type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1
estate.property.tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1
estate.property.offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1
4 changes: 4 additions & 0 deletions estate/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import (
test_estate_property,
test_estate_property_offer,
)
Loading