-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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: create real estate module with basic property managemen… #136
Changes from 3 commits
2bbbbe9
8d72692
00416dd
a3a7461
99a0673
bdf1931
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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,22 @@ | ||
{ | ||
"name": "Real_estate", | ||
"version": "0.1", | ||
"license": "LGPL-3", | ||
"category": "Estate_props", | ||
"author": "sahilpanghal(span)", | ||
"summary": "Real estate module", | ||
"description": "Real estate module", | ||
"installable": True, | ||
"application": True, | ||
"icons": ["static/description/realestate.png"], | ||
"depends": ["base"], | ||
"data": [ | ||
"security/ir.model.access.csv", | ||
"views/estate_property_view.xml", | ||
"views/estate_property_offer_view.xml", | ||
"views/estate_property_type_view.xml", | ||
"views/estate_property_tag_view.xml", | ||
"views/estate_menu.xml", | ||
"views/res_users.xml", | ||
], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
# imported all the models taht are made inside the model folder | ||
from . import estate_property | ||
from . import estate_property_type | ||
from . import estate_property_tag | ||
from . import estate_property_offer | ||
from . import res_users |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,135 @@ | ||||||||||||||||||||||||||||
from odoo import api, models, fields | ||||||||||||||||||||||||||||
from dateutil.relativedelta import relativedelta | ||||||||||||||||||||||||||||
from odoo.exceptions import UserError, ValidationError | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
class EstateProperty(models.Model): | ||||||||||||||||||||||||||||
_name = "estate.property" | ||||||||||||||||||||||||||||
_description = "EstateProperty" | ||||||||||||||||||||||||||||
_order = "id desc" | ||||||||||||||||||||||||||||
# created the field for the estate.property model | ||||||||||||||||||||||||||||
name = fields.Char(required=True, default="Unkown") | ||||||||||||||||||||||||||||
description = fields.Text() | ||||||||||||||||||||||||||||
property_type_id = fields.Many2one("estate.property.type", string="Property Types") | ||||||||||||||||||||||||||||
postcode = fields.Char() | ||||||||||||||||||||||||||||
date_availablity = fields.Date( | ||||||||||||||||||||||||||||
copy=False, default=lambda self: fields.Date.today() + relativedelta(months=3) | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
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( | ||||||||||||||||||||||||||||
[("north", "North"), ("south", "South"), ("east", "East"), ("west", "West")] | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
state = fields.Selection( | ||||||||||||||||||||||||||||
[ | ||||||||||||||||||||||||||||
("new", "New"), | ||||||||||||||||||||||||||||
("offer_recieved", "Offer Recieved"), | ||||||||||||||||||||||||||||
("offer_accepted", "Offer Accepted"), | ||||||||||||||||||||||||||||
("sold", "Sold"), | ||||||||||||||||||||||||||||
("canceled", "Canceled"), | ||||||||||||||||||||||||||||
], | ||||||||||||||||||||||||||||
default="new", | ||||||||||||||||||||||||||||
readonly=True, | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
active = fields.Boolean(default=True) | ||||||||||||||||||||||||||||
saler_id = fields.Many2one( | ||||||||||||||||||||||||||||
"res.users", | ||||||||||||||||||||||||||||
string="Salesperson", | ||||||||||||||||||||||||||||
index=True, | ||||||||||||||||||||||||||||
default=lambda self: self.env.user, | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
buyer_id = fields.Many2one("res.partner", string="Buyer", index=True) | ||||||||||||||||||||||||||||
tag_ids = fields.Many2many("estate.property.tag", string="Tags") | ||||||||||||||||||||||||||||
offer_ids = fields.One2many("estate.property.offer", "property_id") | ||||||||||||||||||||||||||||
total = fields.Integer(compute="_compute_area") | ||||||||||||||||||||||||||||
best_price = fields.Integer(compute="_compute_bestprice") | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created the sql constraints for only accepting positive values for expected price and selling price | ||||||||||||||||||||||||||||
_sql_constraints = [ | ||||||||||||||||||||||||||||
( | ||||||||||||||||||||||||||||
"check_expected_price", | ||||||||||||||||||||||||||||
"CHECK(expected_price > 0.0)", | ||||||||||||||||||||||||||||
"A property expected price must be strictly positive and Grater then Zero.", | ||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||
( | ||||||||||||||||||||||||||||
"check_selling_price", | ||||||||||||||||||||||||||||
"CHECK(selling_price > 0.0)", | ||||||||||||||||||||||||||||
"A property selling price must be positive and Grater then Zero.", | ||||||||||||||||||||||||||||
), | ||||||||||||||||||||||||||||
] | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created the area computation function that will compute area from living area and garden area i.e living area + garden area | ||||||||||||||||||||||||||||
@api.depends("living_area", "garden_area") | ||||||||||||||||||||||||||||
def _compute_area(self): | ||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||
record.total = record.living_area + record.garden_area | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created the best price computation function that will compute best price from offers i.e maximum from the offers | ||||||||||||||||||||||||||||
@api.depends("offer_ids") | ||||||||||||||||||||||||||||
def _compute_bestprice(self): | ||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||
if record.offer_ids: | ||||||||||||||||||||||||||||
max1 = 0 | ||||||||||||||||||||||||||||
for i in record.offer_ids: | ||||||||||||||||||||||||||||
if i.price > max1: | ||||||||||||||||||||||||||||
max1 = i.price | ||||||||||||||||||||||||||||
record.best_price = max1 | ||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
record.best_price = 0 | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created the garden onchange function that will change the values of the dependent fiels when the particular field triger is activated i.e turning on the garden field will affect the dependent fields | ||||||||||||||||||||||||||||
@api.onchange("garden") | ||||||||||||||||||||||||||||
def _onchange_garden(self): | ||||||||||||||||||||||||||||
if self.garden: | ||||||||||||||||||||||||||||
self.garden_area = 10 | ||||||||||||||||||||||||||||
self.garden_orientation = "north" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created a python constraint that will check weather the selling price is grater then the 90% of the expected price | ||||||||||||||||||||||||||||
@api.constrains("selling_price") | ||||||||||||||||||||||||||||
def _check_selling_price(self): | ||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||
if ( | ||||||||||||||||||||||||||||
record.selling_price == 0 | ||||||||||||||||||||||||||||
or record.selling_price >= (90 / 100) * record.expected_price | ||||||||||||||||||||||||||||
): | ||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
raise ValidationError( | ||||||||||||||||||||||||||||
"the selling price cannot be lower than 90'%' of the expected price." | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
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.
Suggested change
You can do something like this |
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# creating a delection check function that will check if the property is sold or not | ||||||||||||||||||||||||||||
@api.ondelete(at_uninstall=False) | ||||||||||||||||||||||||||||
def _check_property(self): | ||||||||||||||||||||||||||||
if any(user.state not in ("new", "canceled") for user in self): | ||||||||||||||||||||||||||||
raise UserError("You can't delete a property that is in process.") | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created the action for the sold button that will chage the state field of the property to sold | ||||||||||||||||||||||||||||
def action_sold(self): | ||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||
if record.state == "canceled": | ||||||||||||||||||||||||||||
raise UserError( | ||||||||||||||||||||||||||||
"This Property couldn't be sold Because it is alredy canceled." | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
elif record.state == "sold": | ||||||||||||||||||||||||||||
raise UserError("This property is alredy Sold.") | ||||||||||||||||||||||||||||
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.
Suggested change
You can do something like this instead of checking conditions separately |
||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
record.state = "sold" | ||||||||||||||||||||||||||||
return True | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# created the action for the cancel button that will change the state of the property to canceled | ||||||||||||||||||||||||||||
def action_cancel(self): | ||||||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||||||
if record.state == "sold": | ||||||||||||||||||||||||||||
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. Try same as above |
||||||||||||||||||||||||||||
raise UserError("This property is alredy sold. You can't cancel it.") | ||||||||||||||||||||||||||||
elif record.state == "canceled": | ||||||||||||||||||||||||||||
raise UserError("This property is alredy canceled.") | ||||||||||||||||||||||||||||
else: | ||||||||||||||||||||||||||||
record.state = "canceled" | ||||||||||||||||||||||||||||
return True |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from odoo import api, models, fields | ||
from dateutil.relativedelta import relativedelta | ||
from datetime import date | ||
from odoo.exceptions import UserError, ValidationError | ||
|
||
|
||
class EstateProperty(models.Model): | ||
_name = "estate.property.offer" | ||
_description = "EstatePropertyOffer" | ||
_order = "price desc" | ||
# created the fields for the estate.property.offer model | ||
name = fields.Char() | ||
price = fields.Float() | ||
status = fields.Selection([("accepted", "Accepted"), ("refused", "Refused")]) | ||
partner_id = fields.Many2one("res.partner") | ||
property_id = fields.Many2one("estate.property") | ||
Validity = fields.Integer(default=7) | ||
deadline = fields.Date(compute="_compute_deadline", inverse="_inverse_deadline") | ||
property_type_id = fields.Many2one( | ||
related="property_id.property_type_id", store=True | ||
) | ||
|
||
# created the sql constraints for only accepting positive value to price | ||
_sql_constraints = [ | ||
( | ||
"check_price", | ||
"CHECK(price > 0.0)", | ||
"An offer price must be strictly positive and Grater then Zero.", | ||
) | ||
] | ||
|
||
# created the compute function to compute deadline as create date + validity | ||
@api.depends("Validity") | ||
def _compute_deadline(self): | ||
for record in self: | ||
if record.create_date: | ||
pass | ||
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. try to avoid using pass |
||
else: | ||
record.create_date = date.today() | ||
record.deadline = record.create_date + relativedelta(days=record.Validity) | ||
|
||
# created the inverse function that will compute the validity when we manually update the deadline i.e deadline - creation date | ||
def _inverse_deadline(self): | ||
for record in self: | ||
record.Validity = ( | ||
record.deadline.toordinal() - record.create_date.toordinal() | ||
) | ||
|
||
# create a create model that will make status as offer recieved when we create the offer | ||
@api.model | ||
def create(self, vals): | ||
record = super().create(vals) | ||
if record.price < record.property_id.best_price: | ||
raise ValidationError( | ||
"One of the offer is present which is best than your offer." | ||
) | ||
record.property_id.state = "offer_recieved" | ||
return record | ||
|
||
# created the action for the accept button that will accept the offer from the offers for a property. | ||
def action_accept(self): | ||
# checks weather any other offer is alredy accepted. if offer is alredy accepted then it will raise an error | ||
if self.property_id.buyer_id: | ||
raise UserError("One of the Offer is alredy selected for this property.") | ||
for record in self: | ||
record.status = "accepted" | ||
record.property_id.buyer_id = record.partner_id.id | ||
record.property_id.selling_price = record.price | ||
record.property_id.state = "offer_accepted" | ||
return True | ||
|
||
# created the action for the refuse button that will refuse the offer from the offers for a property | ||
def action_refuse(self): | ||
for record in self: | ||
record.status = "refused" | ||
record.property_id.buyer_id = "" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class EstateProperty(models.Model): | ||
_name = "estate.property.tag" | ||
_description = "EstatePropertyTag" | ||
_order = "name" | ||
|
||
# created the fields for the estate.property.tag model | ||
name = fields.Char(required=True) | ||
color = fields.Integer(string="Color Index") | ||
|
||
# created the Sql constraints for unique tags | ||
_sql_constraints = [ | ||
("unique_name", "unique(name)", "A property tag name must be unique.") | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from odoo import models, fields, api | ||
|
||
|
||
class EstateProperty(models.Model): | ||
_name = "estate.property.type" | ||
_description = "EstatePropertyType" | ||
_order = "name" | ||
|
||
# created the fields for the estate.property.type model | ||
name = fields.Char(required=True) | ||
property_ids = fields.One2many("estate.property", "property_type_id") | ||
sequence = fields.Integer( | ||
"Sequence", default=1, help="Used to order stages. Lower is better." | ||
) | ||
offer_ids = fields.One2many("estate.property.offer", "property_type_id") | ||
offer_count = fields.Integer(compute="_compute_offer_count", string="Offer Count") | ||
|
||
# created the compute function to compute the offer count | ||
@api.depends("offer_ids") | ||
def _compute_offer_count(self): | ||
for record in self: | ||
record.offer_count = len(record.offer_ids) | ||
|
||
# created the sql constraints for unique property type | ||
_sql_constraints = [ | ||
("unique_name", "unique(name)", "A property type name must be unique.") | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class resuserinherit(models.Model): | ||
_inherit = "res.users" | ||
|
||
property_ids = fields.One2many("estate.property", "saler_id") |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<odoo> | ||
<!-- created the menu items for the model to show inside the module --> | ||
<menuitem id="test_menu_root" name="Real Estate" | ||
web_icon="estate,static/description/icon.png"> | ||
<menuitem id="test_first_level_menu" name="Advertisments"> | ||
<menuitem id="test_model_menu_action" action="estate_property_action" /> | ||
</menuitem> | ||
<menuitem id="test_second_level_menu" name="Settings"> | ||
<menuitem id="test_model_menu_action2" action="estate_property_type_action" /> | ||
<menuitem id="test_model_menu_action3" action="estate_property_tag_action" /> | ||
</menuitem> | ||
</menuitem> | ||
|
||
</odoo> | ||
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. Need a new line at EOF. |
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.
generally, we follow a convention in which we first import all the external libraries and the import from odoo in alphabetical order.
Need to check in other files also