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
23 changes: 23 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
'name': "Real Estate",
'version': '1.0',
'depends': ['base'],
'author': "Odoo S.A.",
'category': 'Customizations',
'description': """
This module is designed to manage real estate properties, including details such as property type, location, price, and status.

It allows users to easily track and organize their real estate assets, making it easier to manage and analyze their property portfolio.
""",
'installable': True,
'application': True,
'license': 'LGPL-3',
'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/estate_menus.xml',
],
}
2 changes: 2 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import (estate_property, estate_property_offer, estate_property_tag,
estate_property_type)
119 changes: 119 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from datetime import date
from dateutil.relativedelta import relativedelta

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


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

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False, default=lambda x: date.today() + relativedelta(months=3))
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([
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West'),
])

property_type_id = fields.Many2one(
"estate.property.type", string="Property Type"
)
salesman_id = fields.Many2one(
"res.partner", string="Salesman", default=lambda self: self.env.user
)
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)

tag_ids = fields.Many2many("estate.property.tag")
offer_ids = fields.One2many(
"estate.property.offer", "property_id", string="Offers"
)
active = fields.Boolean(default=True)
state = fields.Selection([
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
], required=True, default='new')
sequence = fields.Integer(default=1)
total_area = fields.Integer(compute='_compute_total_area')
best_price = fields.Float(compute='_compute_best_price')

_check_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'The expected price must be stricly positive.'
)

_check_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'The selling price must be stricly positive.'
)

@api.constrains('selling_price')
def _check_selling_price_py(self):
for record in self:
if not float_is_zero(record.selling_price, precision_digits=2)\
and float_compare(record.selling_price,
record.expected_price * .9,
precision_digits=2) < 0:
raise exceptions.ValidationError(
"The selling price can't be lower than 90%% of the expected price.") # noqa: E501

@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_price = max(record.offer_ids.mapped(
'price')) if record.offer_ids else None

@api.onchange('garden')
def _onchange_garden(self):
self.garden_area = None
self.garden_orientation = None
if self.garden:
self.garden_area = 10
self.garden_orientation = 'north'

@api.onchange('offer_ids')
def _onchange_offer_ids(self):
if self.offer_ids and self.state == 'new':
self.state = 'offer_received'

def action_set_sold(self):
error = None
for record in self:
if record.state == 'cancelled':
error = _('Cancelled properties cannot be sold')
continue
record.state = 'sold'
if error:
raise exceptions.UserError(error)

def action_set_cancelled(self):
error = None
for record in self:
if record.state == 'sold':
error = _('Sold properties cannot be cancelled')
continue
record.state = 'cancelled'
if error:
raise exceptions.UserError(error)
67 changes: 67 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from datetime import date
from dateutil.relativedelta import relativedelta

from odoo import api, exceptions, fields, models


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

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

partner_id = fields.Many2one("res.partner", required=True)
property_id = fields.Many2one("estate.property", required=True)
property_type_id = fields.Many2one(
related='property_id.property_type_id', stored=True
)
date_deadline = fields.Date(
compute='_compute_deadline_date', inverse='_inverse_deadline_date'
)

_check_offer_price = models.Constraint(
'CHECK(price > 0)',
'The offer price must be stricly positive.'
)

@api.depends('validity')
def _compute_deadline_date(self):
for record in self:
record.date_deadline = (record.create_date or date.today()) + relativedelta(
days=record.validity
)

def _inverse_deadline_date(self):
for record in self:
start_date = (
record.create_date.date() if record.create_date else date.today()
)

record.validity = (record.date_deadline - start_date).days

def action_accept(self):
for record in self:
if any(
offer.status == "accepted" and record.id != offer.id
for offer in record.property_id.offer_ids
):
raise exceptions.UserError(
'A property can only have one accpeted Offer')
record.status = 'accepted'
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id
record.property_id.state = 'offer_accepted'

def action_refuse(self):
for record in self:
record.status = 'refused'
record.property_id.selling_price = None
record.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 Model"
_order = 'name'

name = fields.Char(required=True)
color = fields.Integer()

_check_unique_name = models.Constraint(
'UNIQUE(name)',
'Tag name must be unique.'
)
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 EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = "Real Estate Property Type Model"
_order = 'name'

name = fields.Char(required=True)
property_ids = fields.One2many(
'estate.property', 'property_type_id', string="Properties"
)
offer_ids = fields.One2many(
'estate.property.offer', 'property_type_id', string="Offers"
)
offer_count = fields.Integer(compute='_compute_offer_count')

_check_unique_name = models.Constraint(
'UNIQUE(name)',
'Type name must be unique.'
)

@api.depends('offer_ids')
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids or [])
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
access_estate_property,access_estate_property,model_estate_property,base.group_user,1,1,1,1
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_property_offer,access_estate_property_offer,model_estate_property_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 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_advertisements_menu"
name="Advertisements">
<menuitem id="estate_property_menu_action" action="estate_property_action"/>
</menuitem>
<menuitem id="estate_settings_menu"
name="Settings">
<menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/>
<menuitem id="estate_property_tags_menu_action" action="estate_property_tags_action"/>
</menuitem>
</menuitem>
</odoo>
49 changes: 49 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="estate_property_offer_action" model="ir.actions.act_window">
<field name="name">Property Offer</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_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="Offers" editable="bottom"
decoration-danger="status == 'refused'"
decoration-success="status == 'accepted'"
>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline" string="Deadline"/>
<button name="action_accept" string="Accpet" invisible="status" type="object"
icon="fa-check"/>
<button name="action_refuse" string="Refuse" invisible="status" type="object"
icon="fa-close"/>
</list>
</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="Offer">
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline" string="Deadline"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>
</data>
</odoo>
42 changes: 42 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>

<record id="estate_property_tags_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list,form</field>
</record>


<record id="estate_property_tag_view_form" model="ir.ui.view">
<field name="name">estate.property.tag.form</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<form string="Tag">
<sheet>
<div class="oe_title mb-4">
<h1>
<field name="name" placeholder="e.g. Cozy"/>
</h1>
</div>
<group>
<field name="color" widget="color_picker"/>
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_tag_view_list" model="ir.ui.view">
<field name="name">estate.property.tag.list</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<list string="Tags" editable="bottom">
<field name="name"/>
<field name="color" widget="color_picker"/>
</list>
</field>
</record>
</data>
</odoo>
Loading