Skip to content
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
18 changes: 18 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
'name': "Estate",
'version': '1.0',
'depends': ['base'],
'author': "Victor Decleire",
'application': True,
'license': "LGPL-3",
'data': [
'security/ir.model.access.csv',

'views/estate_property_offer_views.xml',
'views/inherited_model_views.xml',
'views/estate_property_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_type_views.xml',
'views/estate_menus.xml',
],
}
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 inherited_model
92 changes: 92 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from odoo import fields, models, api
from odoo.exceptions import UserError, ValidationError
from odoo.tools.date_utils import add
from odoo.tools.float_utils import float_compare


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

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(copy=False, default=lambda x: add(fields.Date.today(), 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.Float()
garden_orientation = fields.Selection(selection=[('north', 'North'), ('south', 'South'), ('west', 'West'), ('east', 'East')])
active = fields.Boolean(default=True)
state = fields.Selection(
selection=[('new', 'New'), ('received', 'Offer Received'), ('accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')],
default='new', copy=False, required=True)
seller_id = fields.Many2one('res.users', default=lambda self: self.env.user)
buyer_id = fields.Many2one('res.partner', readonly=True)
tag_ids = fields.Many2many('estate.property.tag', string="Tags")
type_id = fields.Many2one('estate.property.type', string="Type")
offer_ids = fields.One2many('estate.property.offer', 'property_id')
total_area = fields.Float(compute="_compute_total_area", readonly=True, copy=False)
best_price = fields.Float(compute="_get_best_price", readonly=True, copy=False)

_check_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'Expected price must be strictly positive.',
)
_check_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'Selling price must be 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')
def _get_best_price(self):
for record in self:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price'))
else:
record.best_price = 0

@api.constrains('selling_price', 'expected_price')
def _check_selling_price(self):
self.ensure_one()
if self.buyer_id and float_compare(self.expected_price, 0.9 * self.selling_price, 0) > 0:
raise ValidationError("The selling price cannot be lower than 90% of the expected price.")

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

@api.ondelete(at_uninstall=False)
def _unlink_if_not_new_or_cancelled(self):
if any(record.state in ['new', 'cancelled'] for record in self):
raise UserError("Can't delete a property which has a state of new or cancelled!")

def action_set_sold(self):
self.ensure_one()
if self.state == 'cancelled':
raise UserError("Cancelled property cannot be set as sold")
self.state = 'sold'
return 1

def action_set_cancelled(self):
self.ensure_one()
if self.state == 'sold':
raise UserError("Sold property cannot be cancelled")
self.state = 'cancelled'
return 1
55 changes: 55 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from odoo import fields, models, api
from odoo.tools.date_utils import add
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_compare


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

price = fields.Float(required=True)
status = fields.Selection(selection=[('accepted', 'Accepted'), ('refused', 'Refused')], copy=False)
partner_id = fields.Many2one('res.partner', required=True)
property_id = fields.Many2one('estate.property', required=True)
create_date = fields.Date(default=fields.Date.today(), readonly=True)
validity = fields.Integer(default=7)
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline")
property_type_id = fields.Many2one(related="property_id.type_id")

_check_price = models.Constraint(
'CHECK(price > 0)',
'Offer price must be strictly positive.',
)

@api.depends('validity', 'create_date')
def _compute_date_deadline(self):
for record in self:
record.date_deadline = add(record.create_date, days=record.validity)

def _inverse_date_deadline(self):
for record in self:
record.validity = (record.date_deadline - record.create_date).days

@api.model
def create(self, vals):
for val in vals:
created_property = self.env['estate.property'].browse(val['property_id'])
if any(float_compare(val['price'], offer.price, 0) < 0 for offer in created_property.offer_ids):
raise ValidationError("A bigger offer already exists")
if created_property.state == 'new':
created_property.state = 'received'
return super().create(vals)

def action_accept_offer(self):
self.ensure_one()
self.status = 'accepted'
self.property_id.buyer_id = self.partner_id
self.property_id.selling_price = self.price
return 1

def action_refuse_offer(self):
for record in self:
record.status = 'refused'
return 1
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 = "Define the tags of the property"
_order = "name"

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

_check_unique_tag = models.Constraint(
'UNIQUE(name)',
'Tag already exists.',
)
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 fields, models, api


class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Define the type of the property"
_order = "sequence, name"

name = fields.Char(required=True)
sequence = fields.Integer('Sequence', default=1, help="Used to order stages. Lower is better.")
property_ids = fields.One2many("estate.property", "type_id")
offer_ids = fields.One2many("estate.property.offer", "property_type_id")
offer_count = fields.Integer(compute="_compute_offer_count", readonly=True)

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

_check_unique_type = models.Constraint(
'UNIQUE(name)',
'Type already exists.',
)
7 changes: 7 additions & 0 deletions estate/models/inherited_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import fields, models


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

property_ids = fields.One2many("estate.property", "seller_id", domain="['|', ('state', '=', 'new'), ('state', '=', '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
access_estate_group_user,estate.group.user,model_estate_property,base.group_user,1,1,1,1
access_estate_type_group_user,estate.type.group.user,model_estate_property_type,base.group_user,1,1,1,1
access_estate_tag_group_user,estate.tag.group.user,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_offer_group_user,estate.offer.group.user,model_estate_property_offer,base.group_user,1,1,1,1
12 changes: 12 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_first_level_menu" name="Advertisements">
<menuitem id="estate_model_menu_action" action="estate_property_model_action"/>
</menuitem>
<menuitem id="estate_sec_level_menu" name="Settings">
<menuitem id="estate_type_model_menu_action" action="estate_property_type_model_action"/>
<menuitem id="estate_tag_model_menu_action" action="estate_property_tag_model_action"/>
</menuitem>
</menuitem>
</odoo>
43 changes: 43 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_offer_from_property_action" model="ir.actions.act_window">
<field name="name">Offers</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">list</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="Channel" editable="bottom" decoration-success="status == 'accepted'" decoration-danger="status == 'refused'">
<field name="price"/>
<field name="partner_id" string="Partner"/>
<field name="validity" string="Validity (days)"/>
<field name="date_deadline" string="Deadline"/>
<button name="action_accept_offer" type="object" icon="fa-check" string="Accept" invisible="status"/>
<button name="action_refuse_offer" type="object" icon="fa-times" string="Refuse" invisible="status"/>
</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="Channel">
<group>
<field name="price"/>
<field name="partner_id" string="Partner"/>
<field name="status"/>
</group>
<group>
<field name="create_date"/>
<field name="validity"/>
<field name="date_deadline"/>
</group>
</form>
</field>
</record>
</odoo>
32 changes: 32 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_tag_model_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_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="Channel" editable="bottom">
<field name="name" string="Tag"/>
</list>
</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="Propreties Tags">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
47 changes: 47 additions & 0 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_type_model_action" model="ir.actions.act_window">
<field name="name">Property Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list,form</field>
</record>

<record id="estate_property_type_view_list" model="ir.ui.view">
<field name="name">estate.property.type.list</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<list string="Channel">
<field name="sequence" widget="handle"/>
<field name="name" string="Type"/>
</list>
</field>
</record>

<record id="estate_property_type_view_form" model="ir.ui.view">
<field name="name">estate.property.type.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form string="Type">
<header>
<button class="oe_stat_button" type="action" name="%(estate.estate_property_offer_from_property_action)d" string="Offers"/>
</header>
<sheet>
<h1>
<field name="name" string="Type"/>
</h1>
<notebook>
<page string="Properties">
<field name="property_ids">
<list>
<field name="name" string="Title"/>
<field name="expected_price"/>
<field name="state" string="Status"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>
Loading