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
16 changes: 16 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "Real Estate",
"version": "1.9",
"depends": [
"base",
],
"data": [
"views/estate_property_type_views.xml",
"views/estate_property_tag_views.xml",
"views/estate_property_offers_list.xml",
"views/estate_property_views.xml",
"views/estate_menus.xml",
"security/ir.model.access.csv",
],
"application": True,
}
4 changes: 4 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from . import estate_property
from . import estate_property_type
from . import estate_property_tag
from . import estate_property_offer
129 changes: 129 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
from datetime import date, datetime, time
from dateutil.relativedelta import relativedelta

from odoo import api, _, exceptions, fields, models
from odoo.orm.utils import ValidationError
from odoo.tools import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = "realestate.properties"
_description = "Real estate properties"

active = fields.Boolean(default=True)
name = fields.Char("Plan Name", required=True, translate=True)
description = fields.Text("Notes")
postcode = fields.Char("Postcode", required=True)
date_availability = fields.Date(
"Availability date",
copy=False,
default=date.today() + relativedelta(months=3),
)
expected_price = fields.Float("Expected price", required=True)
_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)",
_("The expected price should be strictly positive"),
)
selling_price = fields.Float(
"Selling price",
copy=False,
readonly=True,
compute="_compute_selling_price",
)
_check_selling_price = models.Constraint(
"CHECK(selling_price >= 0)",
_("The selling price should be strictly positive"),
)
state = fields.Selection(
[
("new", "New"),
("offer received", "Offer Received"),
("offer accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
required=True,
default="new",
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("realestate.properties.type")
property_tag_ids = fields.Many2many("realestate.properties.tag", string="Tags")
offer_ids = fields.One2many(
"realestate.properties.offer",
"property_id",
)

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

@api.depends("offer_ids")
def _compute_selling_price(self):
for record in self:
record.selling_price = max(
o.price if o.status == "accepted" else 0 for o in record.offer_ids
)

@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

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

record.state = "sold"

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

@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",
),
)
62 changes: 62 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from datetime import datetime, timedelta

from odoo import api, _, exceptions, fields, models


class EstatePropertyOffer(models.Model):
_name = "realestate.properties.offer"
_description = "Real estate property offer"

price = fields.Float("Price", required=True)
_check_selling_price = models.Constraint(
"CHECK(price > 0)",
_("The price should be strictly positive"),
)

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("realestate.properties", required=True)

@api.depends("validity")
def _computed_date_deadline(self):
for offer in self:
create_date = offer.create_date if offer.create_date else datetime.today()
if offer.validity:
offer.date_deadline = create_date + timedelta(
days=offer.validity,
)

def _inverse_validity_period(self):
for offer in self:
create_date = offer.create_date if offer.create_date else datetime.today()
offer.validity = (offer.date_deadline - create_date.date()).days

def action_confirm(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_cancel(self):
for offer in self:
offer.status = "refused"
if offer.property_id.state == "offer accepted":
offer.property_id.state = "new"
offer.property_id.selling_price = 0
offer.property_id.buyer_id = None
12 changes: 12 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import _, fields, models


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

name = fields.Char("Name", required=True)
_unique_name = models.Constraint(
"UNIQUE(name)",
_("Tage name already exists. Tag names must be unique."),
)
12 changes: 12 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import _, fields, models


class EstatePropertyType(models.Model):
_name = "realestate.properties.type"
_description = "Real estate property type"

name = fields.Char("Property type", required=True)
_unique_name = models.Constraint(
"UNIQUE(name)",
_("Property name already exists. Property names must be unique."),
)
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.access_realestate_properties,access_realestate_properties,estate.model_realestate_properties,base.group_user,1,1,1,1
estate.access_realestate_properties_type,access_realestate_properties_type,estate.model_realestate_properties_type,base.group_user,1,1,1,1
estate.access_realestate_properties_tag,access_realestate_properties_tag,estate.model_realestate_properties_tag,base.group_user,1,1,1,1
estate.access_realestate_properties_offer,access_realestate_properties_offer,estate.model_realestate_properties_offer,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="advertisements_menu" name="Advertisement">
<menuitem id="estate_advertisement_property_action_id"
action="estate_advertisement_property_action" />
</menuitem>
<menuitem id="settings_menu" name="Settings">
<menuitem id="estate_settings_property_type_action_id"
action="estate_settings_property_type_action" />
<menuitem id="estate_settings_property_tag_action_id"
action="estate_settings_property_tag_action" />
</menuitem>
</menuitem>
</odoo>
40 changes: 40 additions & 0 deletions estate/views/estate_property_offers_list.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- the root elements of the data file -->
<odoo>
<record id="estate_property_offer_form" model="ir.ui.view">
<field name="name">realestate.property.form</field>
<field name="model">realestate.properties.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" />
<field name="status" />
</group>
</sheet>
</form>
</field>

</record>
<record id="estate_property_type_views_list" model="ir.ui.view">
<field name="name">realestate.property.type.list</field>
<field name="model">realestate.properties.offer</field>
<field name="arch" type="xml">
<list string="Channel">
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="date_deadline" />
<button name="action_confirm" string="Confirm" type="object"
icon="fa-check" />
<button name="action_cancel" string="Cancel" type="object"
icon="fa-times" />
<field name="status" />
</list>
</field>
</record>

</odoo>
44 changes: 44 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- the root elements of the data file -->
<odoo>
<record id="estate_settings_property_tag_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">realestate.properties.tag</field>
<field name="view_mode">list,form</field>
</record>
<record id="estate_property_type_custom_view" model="ir.ui.view">
<field name="name">realestate.property.form</field>
<field name="model">realestate.properties.tag</field>
<field name="arch" type="xml">
<form string="Custom description form">
<sheet>
<h1 class="oe_title mb32">
<field name="name" />
</h1>
</sheet>
</form>
</field>

</record>
<record id="estate_property_type_views_list" model="ir.ui.view">
<field name="name">realestate.property.type.list</field>
<field name="model">realestate.properties.tag</field>
<field name="arch" type="xml">
<list string="Channel">
<field name="name" />
</list>
</field>
</record>
<record id="estate_property_type_views_filter" model="ir.ui.view">
<field name="name">realestate.property.type.filter</field>
<field name="model">realestate.properties.tag</field>
<field name="arch" type="xml">
<search string="Filter string">
<field name="name" />
<filter string="Group by name" name="name"
context="{'group_by': 'name'}" />
</search>
</field>
</record>

</odoo>
Loading