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
21 changes: 21 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
'name': "Estate",
'version': '1.0',
'depends': ['base'],
'author': 'Dhrudeep',
'description': """
This module provides functionality to manage real estate properties.
""",
'data': [
'security/ir.model.access.csv',
'views/estate_property_offer_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_views.xml',
'views/estate_property_maintenance_requests_view.xml',
'views/estate_menus.xml',
],
'category': 'Tutorial',
'license': 'LGPL-3',
'application': True,
}
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 estate_property_maintenance_requests
122 changes: 122 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError


class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'Estate Property Planning'
_order = 'id desc' # defaultvalue = asc

name = fields.Char(required=True, default="Unknown")
description = fields.Text()
postcode = fields.Float()
date_availability = fields.Date(
"Available From", copy=False, default=fields.Datetime.now
)
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(
selection=[
('north', "North"),
('west', "West"),
('east', "East"),
('south', "South"),
],
help="Direction for the garden"
)
active = fields.Boolean(default=True)
state = fields.Selection(
selection=[
('new', "New"),
('offer_received', "Offer Received"),
('offer_accepted', "Offer Accepted"),
('sold', "Sold"),
('cancelled', "Cancelled"),
],
string="Status",
default='new',
)
property_type_id = fields.Many2one(
'estate.property.type', string="Property Type")
seller_ids = fields.Many2one(
'res.users', string="Salesman", default=lambda self: self.env.user
)
buyer_ids = fields.Many2one('res.partner', string="Buyer", copy=False)
tags_ids = fields.Many2many('estate.property.tag')
offer_ids = fields.One2many('estate.property.offer', 'property_id')
total_area = fields.Float(compute='_compute_total_area')
best_price = fields.Float(
"Best offer", compute='_compute_best_price', store=True)

property_maintenance_requests = fields.One2many(
'estate.property.maintenance.requests', 'property_id')
total_maintenance_cost = fields.Float(
compute='_compute_total_maintenance_cost', string="Total Maintenance Cost")

@api.depends('property_maintenance_requests.cost')
def _compute_total_maintenance_cost(self):
for record in self:
record.total_maintenance_cost = (
sum(record.property_maintenance_requests.mapped('cost')
) if record.property_maintenance_requests else 0.0
)

# SQL Constraint
_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")

# Python Constriant
@api.constrains('selling_price', 'expected_price')
def _check_price(self):
if self.buyer_ids and self.selling_price < (self.expected_price * 0.9):
raise ValidationError(
"The selling price must be at least 90% of the expected price! You must reduce the expected price if you want to accept this offer.")

# Compute Methods
# Depends Decorator
@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 _compute_best_price(self):
for record in self:
record.best_price = (
max(record.offer_ids.mapped('price')
) if record.offer_ids else 0.0
)

# Onchange Decorator
@api.onchange('garden')
def _onchange_garden(self):
if self.garden:
self.garden_area = "10"
self.garden_orientation = "north"
else:
self.garden_area = None
self.garden_orientation = None

# Action Methods
def action_sold(self):
if 'cancelled' in self.mapped('state'):
raise UserError("Cancelled properties cannot be sold.")
else:
for record in self:
if record.property_maintenance_requests.status != 'done':
raise UserError(
"Property cannot be sold if there is any maintenance request not done.")
return self.write({'state': 'sold'})

def action_cancel(self):
if 'sold' in self.mapped('state'):
raise UserError("Sold properties cannot be cancelled.")
return self.write({'state': 'cancelled'})
22 changes: 22 additions & 0 deletions estate/models/estate_property_maintenance_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from odoo import api, fields, models
from odoo.exceptions import ValidationError


class EstatePropertyMaintenanceRequests(models.Model):
_name = 'estate.property.maintenance.requests'
_description = 'Estate Property Maintenance Requests'

title = fields.Char()
cost = fields.Float()
status = fields.Selection(
default='new',
copy=False,
selection=[('new', "New"), ('approved', "Approved"), ('done', "Done")],
)
property_id = fields.Many2one('estate.property', required=True)

@api.constrains('status', 'cost')
def _check_cost(self):
if self.status == 'approved' and self.cost <= 0:
raise ValidationError(
"The cost must be greater than zero.")
61 changes: 61 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from dateutil.relativedelta import relativedelta
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("Price")
status = fields.Selection(
copy=False,
selection=[('accepted', "Accepted"), ('refused', "Refused")],
)
partner_id = fields.Many2one(
'res.partner', string="Partner", required=True)
property_id = fields.Many2one('estate.property', required=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.property_type_id', store=True)

# SQL Constraint
_check_offer_price = models.Constraint(
'CHECK(price > 0)', "The selling price must be positive")

# Compute Methods
# Depends Decorator
@api.depends('create_date', 'validity')
def _compute_date_deadline(self):
for offer in self:
date = offer.create_date.date() if offer.create_date else fields.Date.today()
offer.date_deadline = date + relativedelta(days=offer.validity)

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

# Action Funcitons
def action_accept(self):
for record in self:
if record.property_id.state != 'offer_accepted':
record.status = 'accepted'
record.property_id.selling_price = record.price
record.property_id.buyer_ids = self.partner_id
record.property_id.state = 'offer_accepted'
else:
raise UserError('One offer has already been accepted')

def action_refuse(self):
for record in self:
record.status = 'refused'
record.property_id.state = 'new'
record.property_id.selling_price = '0'
record.property_id.buyer_ids = None
29 changes: 29 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from odoo import api, fields, models


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

name = fields.Char(required=True)
color = fields.Integer("Color Index")

# SQL Constraint
_check_name = models.Constraint('unique(name)', "The name must be unique")

local_datetime = fields.Char(
compute='_compute_local_datetime', store=True)

# Copmute Method - Depends Decorator
@api.depends('create_date')
def _compute_local_datetime(self):
for record in self:
if record.create_date:
# user_tz = self.env.user.tz or 'Asia/Kolkata'
local_dt = fields.Datetime.context_timestamp(
record, record.create_date
)
record.local_datetime = local_dt.strftime("%Y-%m-%d %H:%M:%S")
else:
record.local_datetime = False
25 changes: 25 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from odoo import api, fields, models


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

name = fields.Char(required=True)
sequence = fields.Integer()
offer_ids = fields.One2many('estate.property.offer', 'property_type_id')
offer_count = fields.Integer(compute='_compute_offer_count')

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

# SQL Constarint
_check_name = models.Constraint('unique(name)', "The name must be unique")

# For Inline View
property_ids = fields.One2many(
"estate.property", "property_type_id", string="Properties")
6 changes: 6 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
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_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
access_estate_property_maintenance_requests,access_estate_property_maintenance_requests,model_estate_property_maintenance_requests,base.group_user,1,1,1,1
38 changes: 38 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version='1.0' encoding='UTF-8'?>
<odoo>
<menuitem
id="estate_menu_root"
name="estate"/>

<!-- Advertisments -->
<menuitem
id="estate_menu_advertisements"
parent="estate_menu_root"
name="Advertisements"/>

<menuitem
id="estate_advertisements_properties"
parent="estate_menu_advertisements"
action="estate_model_action"/>

<!-- Settings -->
<menuitem
id="estate_menu_settings"
parent="estate_menu_root"
name="Settings"/>

<menuitem
id="estate_view_property_type"
parent="estate_menu_settings"
action="estate_property_type_action"/>

<menuitem
id="estate_view_property_tag"
parent="estate_menu_settings"
action="estate_property_tag_action"/>

<menuitem
id="estate_view_property_maintenance_requests"
parent="estate_menu_settings"
action="estate_property_maintenance_requests_action"/>
</odoo>
42 changes: 42 additions & 0 deletions estate/views/estate_property_maintenance_requests_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version='1.0' encoding='UTF-8'?>
<odoo>

<!-- Form View -->
<record id="estate_property_maintenance_requests_view_form" model="ir.ui.view">
<field name="name">estate.property.maintenance.requests.form</field>
<field name="model">estate.property.maintenance.requests</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="title"/>
<field name="cost"/>
<field name="status"/>
<field name='property_id'/>
</group>
</sheet>
</form>
</field>
</record>

<!-- List View -->
<record id="estate_property_maintenance_requests_view_list" model="ir.ui.view">
<field name="name">estate.property.maintenance.requests.list</field>
<field name="model">estate.property.maintenance.requests</field>
<field name="arch" type="xml">
<list>
<field name="title"/>
<field name="cost"/>
<field name="status"/>
<field name='property_id' string="Property Name"/>
</list>
</field>
</record>

<!-- Action window -->
<record id="estate_property_maintenance_requests_action" model="ir.actions.act_window">
<field name="name">Maintenance Requests</field>
<field name="res_model">estate.property.maintenance.requests</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
Loading