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
19 changes: 19 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Real Estate",
"version": "1.9",
"author": "odoo",
"depends": [
"base",
],
"data": [
"views/estate_property_offer_views.xml",
"views/estate_property_type_views.xml",
"views/estate_property_tag_views.xml",
"views/estate_property_views.xml",
"views/user_properties_views.xml",
"views/estate_menus.xml",
"security/ir.model.access.csv",
],
"application": True,
"license": "LGPL-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 user_properties
136 changes: 136 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
from odoo import api, _, exceptions, fields, models
from odoo.orm.utils import ValidationError
from odoo.tools import float_compare


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

active = fields.Boolean(default=True)
name = fields.Char("Title", required=True)
description = fields.Text("Notes")
postcode = fields.Char("Postcode", required=True)
date_availability = fields.Date(
"Availability date",
copy=False,
default=lambda _: fields.Date.add(fields.Date.today(), months=3),
)
expected_price = fields.Float("Expected Price", required=True)
selling_price = fields.Float(
"Selling price",
copy=False,
readonly=True,
)
state = fields.Selection(
[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
default="new",
required=True,
copy=False,
)
bedrooms = fields.Integer("Bedrooms", default=2)
facades = fields.Integer("Facades")
garage = fields.Boolean("Garage")
garden = fields.Boolean("Garden")
living_area = fields.Integer("Living area (sqm)")
garden_area = fields.Integer("Garden area (sqm)")
total_area = fields.Integer("Total area (sqm)", compute="_compute_total_area")
garden_orientation = fields.Selection(
[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
)
best_offer = fields.Float("Best Offer", compute="_compute_best_price")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
sale_rep_id = fields.Many2one(
"res.users",
string="Salesperson",
default=lambda self: self.env.user,
)
property_type_id = fields.Many2one("estate.property.type")
property_tag_ids = fields.Many2many("estate.property.tag", string="Tags")
offer_ids = fields.One2many(
"estate.property.offer",
"property_id",
)

_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
_("The expected price should be strictly positive"),
)

_check_selling_price = models.Constraint(
"CHECK(selling_price >= 0)",
_("The selling price should be strictly positive"),
)

@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:
prices = record.offer_ids.mapped("price")
record.best_offer = max(prices, default=0)

@api.onchange("offer_ids")
def _onchange_offer(self):
for record in self:
# in case of a delete
if len(record.offer_ids) == 0:
record.state = "new"

@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 = None

@api.constrains("expected_price", "selling_price")
def _check_offer_acceptable_price(self):
for record in self:
if (
record.state == "offer_accepted"
and float_compare(record.selling_price, record.expected_price * 0.9, 3)
<= 0
):
raise ValidationError(
_(
"The selling price should be greater than 90% of the expected price.",
),
)

@api.ondelete(at_uninstall=False)
def _unlink_except_state_is_new_or_cancelled(self):
for record in self:
if record.state in ("new", "cancelled"):
raise exceptions.UserError(
_("Property that is either new or cancelled, can't be deleted."),
)

def action_sold_btn(self):
for record in self:
if record.state == "cancelled":
raise exceptions.UserError(_("Cancelled properties cannot be sold."))
record.state = "sold"

def action_cancelled_btn(self):
for record in self:
if record.state == "sold":
raise exceptions.UserError(_("Sold properties cannot be cancelled."))
record.state = "cancelled"
90 changes: 90 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from odoo import api, _, exceptions, fields, models
from odoo.tools import float_compare


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

price = fields.Float("Price", required=True)
status = fields.Selection(
[("accepted", "Accepted"), ("refused", "Refused")],
copy=False,
)
validity = fields.Integer(
"Validaty (days)",
default=7,
)
date_deadline = fields.Date(
"Deadline",
compute="_computed_date_deadline",
inverse="_inverse_validity_period",
readonly=False,
)
partner_id = fields.Many2one("res.partner", required=True)
property_id = fields.Many2one("estate.property", required=True)
property_type_id = fields.Many2one(
"estate.property.type",
related="property_id.property_type_id",
store=True,
)

_check_selling_price = models.Constraint(
"CHECK(price > 0)",
_("The price should be strictly positive"),
)

@api.depends("create_date", "validity")
def _computed_date_deadline(self):
for offer in self:
create_date = fields.Date.to_date(offer.create_date) or fields.Date.today()
if offer.validity:
offer.date_deadline = fields.Date.add(
create_date,
days=offer.validity,
)

def _inverse_validity_period(self):
for offer in self:
create_date = fields.Date.to_date(offer.create_date) or fields.Date.today()
offer.validity = (offer.date_deadline - create_date).days

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if vals.get("property_id"):
property_record = self.env["estate.property"].browse(
vals["property_id"],
)
offer_price = vals.get("price", 0.0)
if (
property_record.best_offer
and float_compare(offer_price, property_record.best_offer, 2) < 0
):
raise exceptions.UserError(
_("New offer should be better than current best offer (%.2f).")
% property_record.best_offer,
)
if property_record.state == "new":
property_record.state = "offer_received"
return super().create(vals_list)

def action_accept(self):
for offer in self:
if offer.property_id.buyer_id:
raise exceptions.UserError(_("One offer has already been accepted."))
offer.status = "accepted"
offer.property_id.state = "offer_accepted"
offer.property_id.selling_price = offer.price
offer.property_id.buyer_id = offer.partner_id

def action_refuse(self):
for offer in self:
offer.status = "refused"
if offer.property_id.state == "offer_received" and any(
s != "refused" for s in offer.mapped("status")
):
offer.property_id.state = "new"
offer.property_id.selling_price = 0
offer.property_id.buyer_id = None
15 changes: 15 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import _, fields, models


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

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

_unique_name = models.Constraint(
"UNIQUE(name)",
_("Tage name already exists. Tag names must be unique."),
)
23 changes: 23 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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("Property type", required=True)
sequence = fields.Integer("Sequence")
offer_count = fields.Integer("Offers", compute="_compute_offers_count")
property_list_id = fields.One2many("estate.property", "property_type_id")
offer_ids = fields.One2many("estate.property.offer", "property_type_id")

_unique_name = models.Constraint(
"UNIQUE(name)",
_("Property name already exists. Property names must be unique."),
)

@api.depends("offer_ids")
def _compute_offers_count(self):
for offer in self:
offer.offer_count = len(offer.offer_ids)
18 changes: 18 additions & 0 deletions estate/models/user_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from odoo import fields, models


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

property_ids = fields.One2many(
comodel_name="estate.property",
inverse_name="sale_rep_id",
string="Estate Properties",
domain=[
(
"state",
"in",
["new", "offer_received"],
),
],
)
6 changes: 6 additions & 0 deletions estate/security/ir.model.access.csv
Comment thread
sdemeesterde marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_estate_properties,estate.property,model_estate_property,base.group_user,1,1,1,1
access_estate_properties_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1
access_estate_properties_tag,estate.property_tag,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_properties_offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1
access_estate_res_users,estate.res.users,model_res_users,base.group_user,1,1,1,1
14 changes: 14 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<odoo>
<menuitem id="estate_menu_root" name="Real estate">
<menuitem id="advertisement_menu" name="Advertisement">
<menuitem id="estate_property_menu_action"
action="estate_property_action" />
</menuitem>
<menuitem id="settings_menu" name="Settings">
<menuitem id="estate_property_type_menu_action"
action="estate_property_type_action" />
<menuitem id="estate_property_tag_menu_action"
action="estate_property_tag_action" />
</menuitem>
</menuitem>
</odoo>
45 changes: 45 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<record id="estate_property_offer_action" model="ir.actions.act_window">
<field name="name">Property Offers</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">list,form</field>
<field name="domain">[('property_type_id', '=', active_id)]</field>
</record>

<record id="estate_property_offer_view_form" model="ir.ui.view">
<field name="name">estate.property.offer.form</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<form string="Custom description form">
<sheet>
<group>
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="date_deadline" />
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_offer_view_list" model="ir.ui.view">
<field name="name">estate.property.offer.list</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<list string="Channel" editable="bottom" decoration-danger="status == 'refused'"
decoration-success="status == 'accepted'">
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="date_deadline" />
<button name="action_accept" string="Accept" type="object"
icon="fa-check" invisible="status in ('accepted', 'refused')" />
<button name="action_refuse" string="Refuse" type="object"
icon="fa-times" invisible="status in ('accepted', 'refused')" />
</list>
</field>
</record>

</odoo>
Loading