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: adding a new real estate module #314

Draft
wants to merge 9 commits into
base: 18.0
Choose a base branch
from
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': 'Real Estate',
'version': '1.0',
'category': 'tutorials',
'depends': ['base'],
'summary': 'Chapter 2: Server Framework 101',
'description': "",
'installable': True,
'application': True,
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_menus.xml',
],
'license':'LGPL-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_property
from . import estate_property_type
from . import estate_property_tags
from . import estate_property_offer
100 changes: 100 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from odoo.tools import float_compare, float_is_zero
from odoo import api,fields, models, exceptions

#Class of EstateProperty to define fields of database table
class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'Real Estate Properties'

name = fields.Char(string='Name',required=True)
description = fields.Text(string='Description')
postcode = fields.Char(string='Postcode')
date_availability = fields.Date(string='Available From', copy=False, default=fields.Date.add(fields.Date.today(), months=3))
expected_price = fields.Float(string='Expected Price', required=True)
selling_price = fields.Float(string='Selling Price ', readonly=True, copy=False)
bedrooms = fields.Integer(string='Bedrooms', default=2)
living_area = fields.Integer(string='Living Area(sqm)')
facades = fields.Integer(string='Facades')
garage = fields.Boolean(string='Garage')
garden = fields.Boolean(string='Garden')
garden_area = fields.Integer(string='Garden Area(sqm)')
garden_orientation = fields.Selection(
string ='Garden Orientation',
selection=[('north','North'),
('south','South'),
('east','East'),
('west','West')])
active = fields.Boolean('Active',default=True)
state = fields.Selection(
selection=[
('new','New'),
('offer_received','Offer Received'),
('offer_accepted','Offer Accepted'),
('sold','Sold'),('cancelled','Cancelled')],
string='State',
required=True,
copy=False,
default='new')
total_area = fields.Float(string='Total Aream(sqm)',compute='_compute_total_area')
property_type_id = fields.Many2one('estate.property.type', string='Property Type')
user_id=fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user)
partner_id = fields.Many2one('res.partner', string='Buyer',copy=False)
tag_ids = fields.Many2many('estate.property.tags',string='Tags')
offer_ids = fields.One2many('estate.property.offer','property_id',string='Offers') #One2Many field
best_prices = fields.Float(string='Best Offer',compute='_compute_best_offer')

_sql_constraints = [
('check_expected_price','CHECK(expected_price > 0)','Expected Price must be positive'),
('check_selling_price','CHECK(selling_price > 0)','Selling Price must be positive'),
]

#Function of computing total area
@api.depends('living_area','garden_area')
def _compute_total_area(self):
for record in self:
record.total_area=record.living_area+record.garden_area

#Fucntion of deciding best price among available prices
@api.depends('offer_ids.price')
def _compute_best_offer(self):
for property in self:
if property.offer_ids:
property.best_prices = max(property.offer_ids.mapped('price'),default=0.0)
else:
property.best_prices=0.0

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

#Function to perform action when property Sold
def action_to_sold_property(self):
for record in self:
if record.state == 'cancelled':
raise exceptions.UserError('A cancelled property can not be sold')
record.state = 'sold'
return True

#Function to perform action when property Canceled
def action_to_cancel_property(self):
for record in self:
if record.state == 'sold':
raise exceptions.UserError('A sold property can not be cancelled')
record.state = 'cancelled'
return True

#constrain of selling price not fall more lower than 90% expected price
@api.constrains('selling_price','expected_price')
def _check_selling_price(self):
for record in self:
if float_is_zero(record.selling_price,2) != 1:
threshold = record.expected_price * 0.90
if float_compare(threshold,record.selling_price,2) == 1:
raise exceptions.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.")


67 changes: 67 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from dateutil.relativedelta import relativedelta
from odoo import fields,models,api, exceptions

class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Property Offers"

price = fields.Float(string="Price", required=True)
status = fields.Selection(
selection=[
('accepted','Accepted'),
('refused','Refused')],
string="Status",
copy=False,
readonly=True)
partner_id = fields.Many2one("res.partner",string="Partner")
property_id = fields.Many2one("estate.property",string="Property")
validate = fields.Integer(string="Validity(days)",default=7)
date_deadline = fields.Date(string="Deadline",
default=fields.Date.today(),
compute="_compute_deadline",
inverse="_update_validity")

_sql_constraints = [
('check_price','CHECK(price > 0)','Offer Price must be positive'),
]

#Function of implementing comptue field and inverse function on validity days and date dadeline
@api.depends("validate","create_date")
def _compute_deadline(self):
for date in self:
if date.create_date:
date.date_deadline = date.create_date + relativedelta(days=date.validate)
else:
date.date_deadline = fields.Date.today() + relativedelta(days=date.validate)

def _update_validity(self):
for offer in self:
if offer.date_deadline:
offer.validate = (offer.date_deadline - fields.Date.today()).days
else:
offer.validate = 7

#Function to perform action when offer accpeted
def action_accept(self):
for record in self:
if record.status == 'accepted':
raise exceptions.UserError("This property has already accepted offer!")

record.property_id.selling_price = record.price
record.property_id.partner_id = record.partner_id

record.status = 'accepted'

other_offers = self.search([
('property_id', '=', record.property_id.id),
('id', '!=', record.id),
('status', '=', '')
])

other_offers.write({'status':'refused'})

#Function to perform action when offer refused
def action_refuse(self):
for record in self:
record.status = 'refused'

11 changes: 11 additions & 0 deletions estate/models/estate_property_tags.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields,models

class EstatePropertyTyags(models.Model):
_name = "estate.property.tags"
_description = "Property Tags"

name = fields.Char(string="Name", required=True)

_sql_constraints = [
('name_uniq', 'unique (name)', "Tag name already exists!"),
]
11 changes: 11 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields,models

class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Property Types"

name = fields.Char(string="Name", required=True)

_sql_constraints = [
('name_uniq', 'unique (name)', "Type name already exists!"),
]
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
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_tags,access_estate_property_tags,estate.model_estate_property_tags,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
Comment on lines +2 to +5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please explain why there's a 1,1,1,1 at the end?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, those are access rights to particular group user and which are accesses of read, write , create , unlink. as given 1 to all stats that base.group_user has rights to read,write,create and unlink


15 changes: 15 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<odoo>
<!-- Basic menu items -->
<menuitem id="estate_property_menu_root" name="Real Estate">
<menuitem id="estate_property_advertisement_menu" name="Advertisements">
<menuitem id="estate_property_advertisement_action" action="estate_property_action"/>
</menuitem>
<!-- Settings Menu -->
<menuitem id="estate_property_settings_menu" name="Settings">
<!-- Property Type-->
<menuitem id="estate_property_settings_types_action" action="estate_property_type_action"/>
<!-- Property Tags-->
<menuitem id="estate_property_settings_tags_action" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</odoo>
8 changes: 8 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<odoo>
<!-- Property Tag Action views-->
<record id = "estate_property_tag_action" model = "ir.actions.act_window">
<field name ="name">Property Tags</field>
<field name ="res_model">estate.property.tags</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
26 changes: 26 additions & 0 deletions estate/views/estate_property_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<odoo>
<!-- Created actions for the model estate.property.type -->
<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>
<!-- Form view of property type -->
<record id="estate_property_type_form_view" model="ir.ui.view">
<field name="name">estate.property.form</field>
<field name="model">estate.property.type</field>
<field name="arch" type="xml">
<form string="Property Types">
<sheet>
<group>
<group>
<h1 class="mb32">
<field name="name" class="mb16"/>
</h1>
</group>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
115 changes: 115 additions & 0 deletions estate/views/estate_property_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<odoo>
<!-- Created actions for the model estate.property -->
<record id = "estate_property_action" model="ir.actions.act_window">
<field name="name">Properties</field>
<field name="res_model">estate.property</field>
<field name="view_mode">list,form</field>
</record>
<!-- List view of property -->
<record id = "estate_property_list_view" model= "ir.ui.view">
<field name="name">estate.property.tree</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<list string= "Properties" >
<field name="name" string ="Title"/>
<field name="postcode"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="expected_price"/>
<field name="selling_price"/>
<field name="date_availability"/>
<field name="property_type_id"/>
</list>
</field>
</record>
<!-- Form view of property -->
<record id="estate_property_form_view" model="ir.ui.view">
<field name="name">estate.property.view.form</field>
<field name="model">estate.property</field>
<field name="arch" type="xml">
<form string="Estate Property">
<header>
<button string="SOLD" name="action_to_sold_property" type="object" class="oe_highlight"/>
<button string="CANCEL" name="action_to_cancel_property" type="object" class="oe_highlight"/>
</header>
<sheet>
<group>
<h1 class="mb32">
<field name="name" class="mb16"/>
</h1>
</group>
<group>
<group>
<field name="tag_ids" widget="many2many_tags"/>
<field name="state"/>
<field name="property_type_id"/>
<field name="postcode"/>
<field name="date_availability"/>
</group>
<group>
<field name="expected_price"/>
<field name="best_prices"/>
<field name="selling_price"/>
</group>
</group>
<notebook>
<page string="Description">
<group>
<field name="description"/>
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<field name="garage"/>
<field name="garden"/>
<field name="garden_area"/>
<field name="garden_orientation"/>
<field name="total_area"/>
</group>
</page>
<page string="Offers">
<field name="offer_ids" widget="one2many_tags">
<list>
<field name="price">Price</field>
<field name="partner_id">Partner</field>
<field name="validate"/>
<field name="date_deadline"/>
<button name="action_accept" string="Accept" type="object" icon="fa-check"/>
<button name="action_refuse" string="Refuse" type="object" icon="fa-times"/>
<field name="status">Status</field>
</list>
</field>
</page>
<page string="Other Info">
<group>
<field name="user_id"/>
<field name="partner_id"/>
</group>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Property search bar features-->
<record id="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 string="Property Search">
<field name="bedrooms"/>
<field name="living_area"/>
<field name="facades"/>
<field name="garage"/>
<field name="garden"/>
<field name="garden_area"/>
<field name="property_type_id"/>
<separator/>
<filter string="Available" name="date_availability" domain="[('date_availability', '>=', context_today())]"/>
<filter string="New or Offer Recieved" name="state" domain="['|', ('state', '=', 'new'),('state', '=', 'offer_received')]"/>
<group expand="1" string="Group By">
<filter string="Postcode" name="postcode" context="{'group_by':'postcode', 'residual_visible':True}"/>
</group>
Comment on lines +109 to +111
Copy link

@rare-odoo rare-odoo Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this filter placed inside the group tag?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group tag is used to organize filters in search view and the reason that field is placed inside group tag because it defines grouping filter though it can implemented without using group tag.

</search>
</field>
</record>
</odoo>