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': "A Real Estade Advestisement Demo",
'version': '1.0',
'depends': ['base'],
'author': "Odoo S.A.",
'category': 'Sales',
'description': """
Test module for managing real estade advertisement
created as part of the onboarding.
""",
'license': 'LGPL-3',
# data files always loaded at installation
'data': [
'security/ir.model.access.csv',

'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_property_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 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, fields, models, tools
from odoo.exceptions import UserError, ValidationError


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

property_type_id = fields.Many2one('estate.property.type', string='Property Type')

buyer_id = fields.Many2one('res.partner', string='Buyer', copy=False)
salesperson_id = fields.Many2one('res.users', string='Saleswoman', default=lambda self: self.env.user)

tag_ids = fields.Many2many('estate.property.tag', string='Property Tags')

offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offers')

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
'Availale From',
copy=False,
default=fields.Date.add(fields.Date.today(), months=3))
expected_price = fields.Float(required=True)
_check_expected_price = models.Constraint(
'check (expected_price > 0)',
'The expected price can not be negative or zero')
selling_price = fields.Float(readonly=True, copy=False)
_check_selling_price = models.Constraint(
'check (selling_price >= 0)',
'The selling price can not be negative, it can be zero though')
bedrooms = fields.Integer(default=2)
living_area = fields.Integer('Living area (m²)')
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer('Garden area (m²)')
garden_orientation = fields.Selection(
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')],
help="Garden orientation in respect to main compass directions")
active = fields.Boolean(default=True)
state = fields.Selection(
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled')],
default='new',
help="State of the estate property")

total_area = fields.Integer(
'Total area (m²)',
compute='_compute_total_area',
help='Total area of the estate defined as a sum of living and garden area')

best_price = fields.Float(
'Best Offer',
compute='_compute_best_price',
help='Best offer from the availale offers or zero')

@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):
if len(self.offer_ids) == 0:
for record in self:
record.best_price = 0.0
else:
for record in self:
record.best_price = min([offer.price for offer in record.offer_ids])

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

def action_set_property_as_sold(self):
for record in self:
if record.state == 'cancelled':
raise UserError(_("Estate property can not be marked as sold if it was cancelled"))

record.state = "sold"

return True

def action_cancel_property(self):
for record in self:
if record.state == 'sold':
raise UserError(_("Estate property can not be marked as cancelled if it was already sold"))

record.state = "cancelled"

return True

@api.constrains('selling_price', 'expected_price')
def check_selling_price_in_respect_to_expected_price(self):
for record in self:
if not tools.float_is_zero(record.selling_price, 8)\
and record.selling_price < (record.expected_price * 0.9):
raise ValidationError(_("Selling price can not be lower than 90% of the expected price"))

@api.ondelete(at_uninstall=False)
def _unlink_if_state_is_new_or_cancelled(self):
for record in self:
if record.state not in ('new', 'cancelled'):
raise UserError(_("You shall not delete estate properties which are not new or cancelled"))
74 changes: 74 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from odoo import _, api, fields, models
from odoo.exceptions import UserError

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

price = fields.Float()
_check_price = models.Constraint(
'check (price > 0)',
'The offer price can not be negative or zero')
status = fields.Selection(
selection=[
('accepted', 'Accepted'),
('refused', 'Refused')],
copy=False,
help="State of the estate property offer")
partner_id = fields.Many2one('res.partner', string='Partner', required=True)
property_id = fields.Many2one('estate.property', string='Property', required=True)
property_type_id = fields.Many2one(
'estate.property.type',
related='property_id.property_type_id',
store=True)

validity = fields.Integer('Validity (days)', default=7)
date_deadline = fields.Date(
'Deadline',
compute='_compute_date_deadline',
inverse='_onchange_date_deadline',
help='Deadline defined as date from creation separated by validy dates')

@api.depends('validity')
def _compute_date_deadline(self):
for record in self:
if record.create_date:
record.date_deadline = fields.Date.add(record.create_date, days=record.validity)
else:
record.date_deadline = fields.Date.add(fields.Datetime.now(), days=record.validity)

@api.onchange('date_deadline')
def _onchange_date_deadline(self):
for record in self:
if record.create_date:
record.validity = (record.date_deadline - record.create_date.date()).days

def action_accept_offer(self):
for record in self:
if any(o.status == 'accepted' for o in record.property_id.offer_ids):
raise UserError(_("One does not simply accept multiple offers on the property"))

record.status = 'accepted'
record.property_id.buyer_id = record.partner_id
record.property_id.selling_price = record.price

def action_refuse_offer(self):
for record in self:
record.status = 'refused'

def action_reset_status(self):
for record in self:
record.status = None

@api.model
def create(self, vals_list):
for vals in vals_list:
estate_property = self.env['estate.property'].browse(vals['property_id'])

if vals['price'] < estate_property.best_price:
raise UserError(f"You can not create an offer with lower price than est offer: {estate_property.best_price}")

estate_property.state = 'offer_received';

super().create(vals_list)
16 changes: 16 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models

class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Estate property tag, i.e. simple boolean characteristics of the estate outside the normal properties"
_order = "name"

name = fields.Char(required=True)
_check_name = models.Constraint(
'unique (name)',
'The property tag name must be unique, choose different name')
visual_code = fields.Char(required=True, help="Visual character for purpose of brief visualization")
_check_visual_code = models.Constraint(
'unique (visual_code)',
'The property tag visual code must be unique, choose different code')
color = fields.Integer(default=0, help="Colour of the tag")
31 changes: 31 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from odoo import api, fields, models

class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate property type, i.e. type of the building accourding to the use"
_order = "sequence, name"

property_ids = fields.One2many('estate.property', 'property_type_id', string='Property')
offer_ids = fields.One2many(
'estate.property.offer',
'property_type_id')

name = fields.Char(required=True)
_check_name = models.Constraint(
'unique (name)',
'The property type name must be unique, choose different name')
description = fields.Text(help="Description of thus estate property type for better user understanding")
code = fields.Char(required=True, help="Single to double character code for identification when space is scarce")
_check_code = models.Constraint(
'unique (code)',
'The property type code must be unique, choose different code')
sequence = fields.Integer(default=1, help="Ordering sequence, ower is first")
offer_count = fields.Integer(
'Amount of offers',
compute='_compute_offer_count',
help='Computed field with amount of the offers related to this type')

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


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

property_ids = fields.One2many(
'estate.property',
'salesperson_id',
string='Available properties')
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_estate_property,access_estate_property,estate.model_estate_property,base.group_user,1,1,1,1
estate.access_estate_property_type,access_estate_property_type,estate.model_estate_property_type,base.group_user,1,1,1,1
estate.access_estate_property_tag,access_estate_property_tag,estate.model_estate_property_tag,base.group_user,1,1,1,1
estate.access_estate_property_offer,access_estate_property_offer,estate.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_advertisements_first_level_menu" name="Advertisements">
<menuitem id="estate_property_menu_action" action="estate_property_action"/>
</menuitem>
<menuitem id="estate_settings_first_level_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>
94 changes: 94 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?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>
<field name="help" type="html">
<p class="o_view_nocontent_neutral_face">
Create a new estate property offer.
</p><p>
The estate properties offers are bids for estate buy.
</p><p>
... go and create some 🔏
</p>
</field>
</record>

<record id="estate_property_offer_view_tree" 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="Estate Property Offer Tree and or List"
editable="bottom">
<field name="price"/>
<field name="partner_id"/>
<field name="status"/>
</list>
</field>
</record>

<record id="estate_property_offer_view_search" model="ir.ui.view">
<field name="name">estate.property.offer.search</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<search>
<field name="status"/>
<field name="partner_id"/>
<field name="property_id"/>

<!--filter
string="Available"
name="available"
help="display only available estates"
domain="['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]"/>

<filter
string="Postcode"
name="groupby_postcode"
context="{'group_by': 'postcode'}"/-->

</search>
</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="Estate Property Offer Form">
<header>
<button
name="action_accept_offer"
type="object" string="Accept"
title="Mark property as sold"
invisible="status in ('accepted', 'refused')"/>
<button
name="action_refuse_offer"
type="object"
string="Refuse"
title="Cancel this property"
invisible="status in ('accepted', 'refused')"/>
<button
name="action_reset_status"
type="object"
string="Reset status"
title="Reset status of this offer"
invisible="status not in ('accepted', 'refused')"/>
</header>
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>

</odoo>
Loading