-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
base: 18.0
Are you sure you want to change the base?
Changes from all commits
77f25c2
e92a44a
2c4fda3
bd252ea
fdcb0fe
ac85c82
7518a14
f22f496
cbe1423
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
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' | ||
} |
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 |
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.") | ||
|
||
|
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' | ||
|
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!"), | ||
] |
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!"), | ||
] |
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 | ||
|
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> |
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> |
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> |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this filter placed inside the group tag? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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> |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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