Skip to content
Empty file added DO_NOT_REMOVE_THIS.txt
Empty file.
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': "Estate",

'author': "Odoo",
'website': "https://www.odoo.com",

'category': 'Tutorials',
'version': '1.0',

'depends': ['base', 'web'],
'data': [
'views/estate_property_offer_view.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_search_view.xml',
'views/estate_property_views.xml',
'views/estate_menus.xml',
'security/ir.model.access.csv',
],
'application': True,
'installable': True,
'license': 'AGPL-3'
}
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
from . import property_type
from . import property_tag
from . import offer
103 changes: 103 additions & 0 deletions estate/models/estate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare, float_is_zero


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

name = fields.Char("Property Name", required=True)
description = fields.Text("Description")
postcode = fields.Char("Postcode")
date_availability = fields.Date("Available From", default=fields.Date.add(fields.Date.today(), months=3))
expected_price = fields.Float("Expected Price", required=True)
selling_price = fields.Float("Selling Price", readonly=True, copy=False)
bedrooms = fields.Integer("Bedrooms", default=2)
living_area = fields.Integer("Living Area (sqm)")
facades = fields.Integer("Facades")
garage = fields.Boolean("Garage", default=True)
garden = fields.Boolean("Garden", default=True)
garden_area = fields.Integer("Garden Area (sqm)")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
('north', "North"),
('south', "South"),
('east', "East"),
('west', "West")
],
required=True)
status = fields.Selection(
string="Status",
selection=[
('new', "New"),
('offer_received', "Offer Received"),
('offer_accepted', "Offer Accepted"),
('sold', "Sold"),
('canceled', "Canceled")
],
default='new',
required=True)
active = fields.Boolean("Active", default=True)
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="Salesperson", default=lambda self: self.env.user)
tag_ids = fields.Many2many('estate.property.tag', string="Tags")
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers")
total_area = fields.Integer("Total Area (sqm)", compute='_compute_total_area')
best_price = fields.Float("Best Offer", compute='_compute_best_price')

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

_check_selling_price = models.Constraint(
"CHECK(selling_price >= 0)",
"The 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.price')
def _compute_best_price(self):
for record in self:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0.0

@api.onchange('offer_ids')
def _onchange_property_status(self):
for record in self:
if record.status == 'new' and len(record.offer_ids) > 0:
record.status = 'offer_received'

@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 = False

def action_mark_sold(self):
for record in self:
if record.status == "canceled":
raise UserError(self.env._("Canceled properties cannot be sold."))
record.status = "sold"

def action_mark_cancel(self):
for record in self:
if record.status == "sold":
raise UserError(self.env._("Sold properties cannot be canceled."))
record.status = "canceled"

@api.constrains('selling_price')
def _check_selling_price(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 * 0.9, precision_digits=2) < 0:
raise ValidationError(self.env._("The selling price must be atleast 90% of the expected price! You must reduce the expected price to accept the offer"))
53 changes: 53 additions & 0 deletions estate/models/offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from odoo import api, fields, models


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

price = fields.Float("Offer Price")
status = fields.Selection(
string="Status",
selection=[('accepted', "Accepted"), ('refused', "Refused")],
copy=False)
partner_id = fields.Many2one('res.partner', string="Partner", required=True)
property_id = fields.Many2one('estate.property', string="Property", required=True)
validity = fields.Integer('Validity (days)', default=7)
date_deadline = fields.Date("Deadline", compute='_compute_date_deadline', inverse="_inverse_date_deadline")
property_type_id = fields.Many2one(related='property_id.property_type_id', string="Property Type", store=True, readonly=True)

_check_price = models.Constraint(
"CHECK(price > 0)",
"The offer price must be strictly positive"
)

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

def _inverse_date_deadline(self):
for record in self:
create_date = record.create_date if record.create_date else fields.Date.today()
record.validity = (record.date_deadline - create_date.date()).days

def action_accept_offer(self):
for record in self:
record.status = "accepted"
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id
record.property_id.status = "offer_accepted"

# Reject other offers
other_offers = record.property_id.offer_ids.filtered(lambda o: o.id != record.id)
other_offers.action_reject_offer()

def action_reject_offer(self):
for record in self:
record.status = "refused"
if record.property_id.status == "offer_accepted" and record.property_id.buyer_id == record.partner_id:
record.property_id.selling_price = 0.0
record.property_id.buyer_id = False
record.property_id.status = "offer_received"
15 changes: 15 additions & 0 deletions estate/models/property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import fields, models


class PropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Estate Property Tag"
_order = "name"

name = fields.Char("Tag Name", required=True)
color = fields.Integer("Color")

_unique_name = models.Constraint(
"UNIQUE(name)",
"The property tag name must be unique"
)
23 changes: 23 additions & 0 deletions estate/models/property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from odoo import api, fields, models


class PropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate Property Type"
_order = "sequence, name"

name = fields.Char("Property Type", required=True)
property_ids = fields.One2many('estate.property', 'property_type_id', string="Properties")
sequence = fields.Integer("Sequence", default=1, help="Used to order property types")
offer_ids = fields.One2many(related='property_ids.offer_ids', string="Offers", readonly=True)
offer_count = fields.Integer("Offer Count", compute='_compute_offer_count')

_unique_name = models.Constraint(
"UNIQUE(name)",
"The property 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)
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_model,access_estate_model,model_estate_property,base.group_user,1,1,1,1
access_property_type_model,access_property_type_model,model_estate_property_type,base.group_user,1,1,1,1
access_property_tag_model,access_property_tag_model,model_estate_property_tag,base.group_user,1,1,1,1
access_property_offer_model,access_property_offer_model,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="Estate">
<menuitem id="estate_menu_advertisements" name="Advertisements">
<menuitem id="estate_menu_properties" name="Properties" action="estate_property_action"/>
</menuitem>
<menuitem id="estate_menu_settings" name="Settings">
<menuitem id="estate_menu_property_types" name="Property Types" action="estate_property_type_action"/>
<menuitem id="estate_menu_propert_tags" name="Property Tags" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</odoo>
22 changes: 22 additions & 0 deletions estate/views/estate_property_offer_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_estate_property_offer" model="ir.actions.act_window">
<field name="name">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>
</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>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
</list>
</field>
</record>
</odoo>
23 changes: 23 additions & 0 deletions estate/views/estate_property_search_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<odoo>
<record id="action_estate_property_search" model="ir.ui.view">
<field name="name">estate.property.search</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<search>
<field name="name" string="Title"/>
<field name="postcode"/>
<field name="expected_price"/>
<field name="bedrooms"/>
<field name="living_area" filter_domain="[('living_area', '>=', self)]"/>
<field name="facades"/>
<field name="status"/>
<filter string="Archived" name="inactive" domain="[('active', '=', False)]"/>
<filter string="Status" name="availability" domain="[('status', 'in', ['new', 'offered'])]"/>
<filter string="Available" name="available" domain="[('active', '=', True)]"/>
<group>
<filter string="Postcode" name="postcode" context="{'group_by':'postcode'}"/>
</group>
</search>
</field>
</record>
</odoo>
18 changes: 18 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_tag_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 editable="bottom">
<field name="name"/>
</list>
</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_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>
<field name="sequence" widget="handle"/>
<field name="name" />
</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="Properties List">
<button name="%(estate.action_estate_property_offer)d" string="Offers" type="action" class="oe_stat_button" icon="fa-money">
<field name="offer_count" widget="statinfo" string="Offers"/>
</button>
<sheet>
<notebook>
<page string="Properties">
<h1>
<field name="name"/>
</h1>
<field name="property_ids">
<list>
<field name="name" string="Title"/>
<field name="expected_price"/>
<field name="status"/>
</list>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>
Loading