Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADD] estate: Tutorial chapters #194

Draft
wants to merge 15 commits into
base: 18.0
Choose a base branch
from
Draft
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
28 changes: 28 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
'name': 'Real estate',
'version': '0.0',
'depends': [
'base',
],
'data': [
'security/ir.model.access.csv',
'security/security.xml',

'views/estate_property_views.xml',
'views/ep_tag_views.xml',
'views/ep_offer_views.xml',
'views/ep_type_views.xml',

'views/res_users_views.xml',

'views/estate_menu_views.xml',

"data/master_data.xml",
],
'demo': [
'demo/demo_data.xml',
],
'application': True,
'license': 'AGPL-3',
'category': 'Real Estate/Brokerage',
}
23 changes: 23 additions & 0 deletions estate/data/master_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_type_0" model="estate.property.type">
<field name="name">Residential</field>
<field name="sequence">1</field>
<field name="description">Residential property type</field>
</record>
<record id="estate_property_type_1" model="estate.property.type">
<field name="name">Commercial</field>
<field name="sequence">2</field>
<field name="description">Commercial property type</field>
</record>
<record id="estate_property_type_2" model="estate.property.type">
<field name="name">Industrial</field>
<field name="sequence">3</field>
<field name="description">Industrial property type</field>
</record>
<record id="estate_property_type_3" model="estate.property.type">
<field name="name">Land</field>
<field name="sequence">4</field>
<field name="description">Land property type</field>
</record>
</odoo>
73 changes: 73 additions & 0 deletions estate/demo/demo_data.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_demo_0" model="estate.property">
<field name="name">Big Villa</field>
<field name="description">A nice and big villa</field>
<field name="postcode">12345</field>
<field name="date_availability">2020-02-02</field>
<field name="expected_price">1600000</field>
<field name="bedrooms">6</field>
<field name="living_area">100</field>
<field name="facades">4</field>
<field name="garage">true</field>
<field name="garden">true</field>
<field name="garden_area">100000</field>
<field name="garden_orientation">south</field>
<field name="property_type_id" ref="estate_property_type_0"/>
</record>
<record id="estate_property_demo_1" model="estate.property">
<field name="name">Trailer home</field>
<field name="description">Home in a trailer park</field>
<field name="postcode">54321</field>
<field name="date_availability">1970-01-01</field>
<field name="expected_price">100000</field>
<field name="bedrooms">1</field>
<field name="living_area">10</field>
<field name="facades">4</field>
<field name="garage">false</field>
<field name="garden">false</field>
<field name="property_type_id" ref="estate_property_type_0"/>
<field name="state">cancelled</field>
<field name="selling_price">120000</field>
</record>
<record id="estate_property_demo_2" model="estate.property">
<field name="name">Trailer home 2</field>
<field name="description">Home in another trailer park</field>
<field name="postcode">54322</field>
<field name="date_availability">1970-01-01</field>
<field name="expected_price">100000</field>
<field name="bedrooms">1</field>
<field name="living_area">10</field>
<field name="facades">4</field>
<field name="garage">false</field>
<field name="garden">false</field>
<field name="property_type_id" ref="estate_property_type_0"/>
<field name="offer_ids" eval="[
Command.create({
'partner_id': ref('base.res_partner_12'),
'price': 99900,
'validity': 7,
}),]"/>
</record>
<record id="estate_property_offer_demo_0" model="estate.property.offer">
<field name="property_id" ref="estate_property_demo_0"/>
<field name="partner_id" ref="base.res_partner_12"/>
<field name="price">10000</field>
<field name="date_deadline" eval="datetime.now().date() + timedelta(days=14)"/>
</record>
<function model="estate.property.offer" name="action_refuse" eval="[ref('estate_property_offer_demo_0')]"/>
<record id="estate_property_offer_demo_1" model="estate.property.offer">
<field name="property_id" ref="estate_property_demo_0"/>
<field name="partner_id" ref="base.res_partner_12"/>
<field name="price">1500000</field>
<field name="date_deadline" eval="datetime.now().date() + timedelta(days=14)"/>
</record>
<function model="estate.property.offer" name="action_accept" eval="[ref('estate_property_offer_demo_1')]"/>
<record id="estate_property_offer_demo_2" model="estate.property.offer">
<field name="property_id" ref="estate_property_demo_0"/>
<field name="partner_id" ref="base.res_partner_2"/>
<field name="price">1500001</field>
<field name="date_deadline" eval="datetime.now().date() + timedelta(days=14)"/>
</record>
<function model="estate.property.offer" name="action_refuse" eval="[ref('estate_property_offer_demo_2')]"/>
</odoo>
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 res_users
117 changes: 117 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from odoo import api
from odoo import fields
from odoo import models
from odoo.exceptions import UserError
from odoo.exceptions import ValidationError
from odoo.tools.float_utils import float_is_zero
from odoo.tools.float_utils import float_compare


class EstateProperty(models.Model):
_name = 'estate.property'
_description = "Estate property"
_order = "id desc"
# Constraints
_sql_constraints = [
('check_name_unique', 'UNIQUE(name)',
'The property title must be unique'),
('check_price_positive', 'CHECK(expected_price > 0)',
'The expected price must be strictly positive.'),
]

name = fields.Char("Title", required=True, translate=True)
description = fields.Text("Property description")
postcode = fields.Char("Postcode")
date_availability = fields.Date("Date of availability", default=lambda _self: fields.Date.add(fields.Date.today(), days=30), copy=False)
expected_price = fields.Float("Expected price", required=True)
selling_price = fields.Float("Selling price", readonly=True, copy=False)
bedrooms = fields.Integer("No. of bedrooms", default=2)
living_area = fields.Integer("Living area (sqm)")
facades = fields.Integer("No. of facades")
garage = fields.Boolean("Has a garage")
garden = fields.Boolean("Has a garden", default=False)
garden_area = fields.Integer("Garden area (sqm)", default=0)
garden_orientation = fields.Selection(
string="Garden orientation",
selection=[
('north', "North"),
('south', "South"),
('east', "East"),
('west', "West"),
],
default=None,
)
# Reserved
active = fields.Boolean("Active", default=True)
state = fields.Selection(
string="Status",
selection=[
('new', "New"),
('recieved', "Offer Received"),
('accepted', "Offer Accepted"),
('sold', "Sold"),
('cancelled', "Cancelled"),
],
required=True,
copy=False,
default='new',
)
# Relational
property_type_id = fields.Many2one('estate.property.type', string="Type")
tag_ids = fields.Many2many('estate.property.tag', string="Tags")
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers")
buyer_id = fields.Many2one('res.partner', string="Buyer")
salesperson_id = fields.Many2one('res.users', string="Salesperson", default=lambda self: self.env.user)
company_id = fields.Many2one('res.company', string="Company", default=lambda self: self.env.company)
# Computed
area_total = fields.Float("Total area", compute='_compute_area_total')
best_offer = fields.Float("Best offer", compute='_compute_best_offer')

@api.constrains('expected_price', 'selling_price')
def _check_price_good(self):
for record in self:
if (not float_is_zero(record.selling_price, precision_digits=3)) and (float_compare(record.selling_price, record.expected_price * 0.9, precision_digits=3) < 0):
raise ValidationError(self.env._("The offer price is unaccetably low!"))

def _compute_area_total(self):
for record in self:
record.area_total = record.living_area + record.garden_area

@api.depends('offer_ids.price')
def _compute_best_offer(self):
for record in self:
record.best_offer = 0 if not record.offer_ids else max(offer.price for offer 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 action_set_sold(self):
for record in self:
if record.state == 'cancelled':
raise UserError(self.env._("The property is already cancelled."))
elif not record.buyer_id: # Checking for buyer_id to be set instead of going through the offers in search of an accepted one
raise UserError(self.env._("The property does not have any accepted offers."))
else:
record.state = 'sold'
return True

def action_set_cancelled(self):
for record in self:
if record.state != 'sold':
record.state = 'cancelled'
else:
raise UserError(self.env._("The property is already sold."))
return True

@api.ondelete(at_uninstall=True)
def prevent_delete_record(self):
for record in self:
if record.state not in ('new', 'cancelled'):
raise UserError(self.env._("Cannot delete a property with active offers (not New/Cancelled)."))
return True
68 changes: 68 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from odoo import api
from odoo import fields
from odoo import models
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_compare


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = "Property offers"
_order = "price desc"
# Constrains
_sql_constraints = [
('check_price_positive', 'CHECK(price > 0)',
'The offer price must be strictly positive.'),
]

name = fields.Char("Name", required=False, default="- no name -")
price = fields.Float("Offer price", required=True)
validity = fields.Integer("Validity time", default=7)
# Reserved
state = fields.Selection(
string="Status",
selection=[
('accepted', "Accepted"),
('refused', "Refused"),
],
default=None,
)
# Relational
property_id = fields.Many2one('estate.property', string="Property", required=True)
partner_id = fields.Many2one('res.partner', string="Buyer", required=True)
property_type_id = fields.Many2one(related='property_id.property_type_id')
# Computed
date_deadline = fields.Date("Deadline date", compute='_compute_deadline', inverse='_inverse_deadline')

@api.depends('validity', 'create_date')
def _compute_deadline(self):
for record in self:
record.date_deadline = fields.Date.add(record.create_date or fields.Date.today(), days=record.validity)

def _inverse_deadline(self):
for record in self:
start_date = (record.create_date and record.create_date.date()) or fields.Date.today()
record.validity = (record.date_deadline - start_date).days

def action_accept(self):
for record in self:
record.state = 'accepted'
record.property_id.state = 'accepted'
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id
return True

def action_refuse(self):
for record in self:
record.state = 'refused'
return True

@api.model
def create(self, vals):
property = self.env['estate.property'].browse(vals['property_id'])
if property.state != 'new' and (float_compare(vals['price'], property.best_offer, precision_digits=3) < 0):
raise UserError(self.env._("A better priced offer already exists."))
elif property.state in ('sold', 'cancelled'):
raise UserError(self.env._("Cannot add offer to a property that is already sold or cancelled."))
property.state = 'recieved'
return super().create(vals)
17 changes: 17 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from odoo import fields
from odoo import models


class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = "Property tag"
_order = "name asc"
# Constraints
_sql_constraints = [
('check_name_unique', 'UNIQUE(name)',
'The property type name must be unique'),
]

name = fields.Char("Tag", required=True, translate=True)
description = fields.Text("Tag description")
color = fields.Integer()
27 changes: 27 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from odoo import fields
from odoo import models
from odoo import api


class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = "Property type"
_order = "name asc"
# Constraints
_sql_constraints = [
('check_name_unique', 'UNIQUE(name)',
'The property type name must be unique'),
]

name = fields.Char("Type", required=True, translate=True)
sequence = fields.Integer('Sequence', default=1, help="Used to order types manually.")
description = fields.Text("Type description")
# Relational
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("Offer count", compute='_compute_offer_count')

@api.depends('offer_ids')
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids)
9 changes: 9 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from odoo import fields
from odoo import models


class ResUsers(models.Model):
_inherit = 'res.users'

# Relational
estate_property_ids = fields.One2many('estate.property', 'salesperson_id', string="Estate properties", domain='[("state", "in", ["new", "recieved"])]')
9 changes: 9 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
id,name,model_id/id,group_id/id,perm_read,perm_write,perm_create,perm_unlink
access_estate_property,estate_property,model_estate_property,estate.estate_group_user,1,1,1,0
access_estate_property_type,estate_property_type,model_estate_property_type,estate.estate_group_user,1,0,0,0
access_estate_property_tag,estate_property_tag,model_estate_property_tag,estate.estate_group_user,1,0,0,0
access_estate_property_offer,estate_property_offer,model_estate_property_offer,estate.estate_group_user,1,1,1,1
access_estate_property,estate_property,model_estate_property,estate.estate_group_manager,1,1,1,0
access_estate_property_type,estate_property_type,model_estate_property_type,estate.estate_group_manager,1,1,1,1
access_estate_property_tag,estate_property_tag,model_estate_property_tag,estate.estate_group_manager,1,1,1,1
access_estate_property_offer,estate_property_offer,model_estate_property_offer,estate.estate_group_manager,1,1,1,1
Loading