-
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: added new real estate module #108
Changes from 6 commits
4e7a1d6
bf62bdf
f855aea
f0ae4ec
947622a
5e7c24a
250a74b
0e9c0c0
8e13d18
d811ef0
3c09613
453d964
0937960
44a3499
550b9d0
92478f7
456c8ed
335e94c
6057707
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,21 @@ | ||
{ | ||
"name": "Real Estate", | ||
"version": "1.0", | ||
"license": "LGPL-3", | ||
"category": "Real Estate", | ||
"summary": "Manage properties", | ||
"depends": ["base_setup", "mail"], | ||
"data": [ | ||
"security/ir.model.access.csv", | ||
"views/real_estate_property.xml", | ||
"views/estate_property_tree_views.xml", | ||
"views/estate_property_offer_view.xml", | ||
"views/property_type_views.xml", | ||
"views/property_tag.xml", | ||
"views/res_users_view.xml", | ||
"views/real_estate_menu.xml", | ||
], | ||
"installable": True, | ||
"application": True, | ||
"auto_install": False, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from . import estate_property | ||
from . import test_model | ||
from . import property_type | ||
from . import property_tag | ||
from . import property_offer | ||
from . import inherited_model |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
from odoo import models, fields, api | ||
from odoo.exceptions import UserError, ValidationError | ||
|
||
|
||
class MyModel(models.Model): | ||
_name = "estate_property" | ||
_description = "Real Estate" | ||
_inherit = ["mail.thread", "mail.activity.mixin"] | ||
_order = "id desc" | ||
|
||
name = fields.Char(string="Title", required=True) | ||
description = fields.Text(string="Description") | ||
postcode = fields.Char(string="Postcode") | ||
date_availability = fields.Date(string="Available From") | ||
expected_price = fields.Float(string="Expected Price") | ||
selling_price = fields.Float(string="Selling Price") | ||
bedrooms = fields.Integer(string="Bedrooms") | ||
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( | ||
selection=[ | ||
("north", "North"), | ||
("south", "South"), | ||
("east", "East"), | ||
("offer receivedwest", "West"), | ||
], | ||
) | ||
active = fields.Boolean(default=True) | ||
state = fields.Selection( | ||
string="Status", | ||
selection=[ | ||
("new", "New"), | ||
("offer received", "Offer Received"), | ||
("offer accepted", "Offer Accepted"), | ||
("sold", "Sold"), | ||
("cancelled", "Cancelled"), | ||
], | ||
tracking=True, | ||
default="new", | ||
) | ||
|
||
salesman_id = fields.Many2one( | ||
"res.users", # The model this field relates to | ||
string="Salesman", # Label for the field | ||
default=lambda self: self.env.user.partner_id, # Set default to the current user | ||
) | ||
buyer_id = fields.Many2one( | ||
"res.partner", # The model this field relates to | ||
string="Buyer", # Label for the field | ||
copy=False, # Prevent the field value from being copied when duplicating the record | ||
) | ||
property_type_id = fields.Many2one( | ||
"real.estate.property.type", string="Property Type" | ||
) | ||
tag_ids = fields.Many2many("real.estate.property.tag", string="tags") | ||
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") | ||
total_area = fields.Float(compute="_compute_total_area") | ||
best_offer = fields.Float(compute="_compute_best_offer") | ||
|
||
@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", "best_offer") | ||
def _compute_best_offer(self): | ||
for record in self: | ||
if record.offer_ids: | ||
record.best_offer = max(record.offer_ids.mapped("price")) | ||
else: | ||
record.best_offer = 0.0 | ||
|
||
@api.onchange("garden") | ||
def _onchange_garden(self): | ||
if self.garden: | ||
self.garden_area = 10 | ||
self.garden_orientation = "north" | ||
else: | ||
self.garden_area = False | ||
self.garden_orientation = False | ||
|
||
def action_cancel(self): | ||
for record in self: | ||
if record.state == "sold": | ||
raise UserError("Sold property cannot be cancelled.") | ||
record.state = "cancelled" | ||
|
||
def action_sold(self): | ||
for record in self: | ||
if record.state == "cancelled": | ||
raise UserError("Cancelled property cannot be sold.") | ||
record.state = "sold" | ||
|
||
@api.depends("offer_ids.price", "selling_price") | ||
def _compute_best_offer(self): | ||
for record in self: | ||
if record.offer_ids: | ||
record.best_offer = max(record.offer_ids.mapped("price")) | ||
else: | ||
record.best_offer = 0.0 | ||
|
||
_sql_constraints = [ | ||
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. Generally, we define SQL constraints just after the field declarations. |
||
("property_name", "unique(name)", "The property name must be unique!"), | ||
( | ||
"selling_price", | ||
"check(selling_price >= 0)", | ||
"A property selling price must be strictly positive.", | ||
), | ||
( | ||
"expected_price", | ||
"CHECK(expected_price >= 0)", | ||
"A property expected price must be strictly positive.", | ||
), | ||
] | ||
|
||
@api.constrains("selling_price", "expected_price") | ||
def _check_selling(self): | ||
for record in self: | ||
if record.selling_price > 0: | ||
price_per = (record.expected_price * 90) / 100 | ||
if record.selling_price < price_per: | ||
raise ValidationError( | ||
"selling price cannot be lower than 90% of the expected price." | ||
) | ||
|
||
@api.ondelete(at_uninstall=False) | ||
def _check_state_before_delete(self): | ||
for record in self: | ||
if record.state not in ["new", "cancelled"]: | ||
raise UserError( | ||
"You cannot delete a property that is not in 'New' or 'Canceled' state." | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class InheritedModel(models.Model): | ||
_inherit = "res.users" | ||
|
||
property_ids = fields.One2many( | ||
"estate_property", | ||
"salesman_id", | ||
string="Offers", | ||
domain="['|',('state', '=', 'offer received'),('state', '=', 'offer accepted')]", | ||
) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,71 @@ | ||||||||||||||||||||||
from odoo import api, models, fields | ||||||||||||||||||||||
from dateutil.relativedelta import relativedelta | ||||||||||||||||||||||
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
We follows a convention in which we first import the external libraries and then imports from odoo in alphabetically order. |
||||||||||||||||||||||
from odoo.exceptions import UserError | ||||||||||||||||||||||
|
||||||||||||||||||||||
|
||||||||||||||||||||||
class EstatePropertyOffer(models.Model): | ||||||||||||||||||||||
_name = "estate.property.offer" | ||||||||||||||||||||||
_description = "Estate Property Offer" | ||||||||||||||||||||||
_order = "price desc" | ||||||||||||||||||||||
|
||||||||||||||||||||||
price = fields.Float(string="Price", required=True) | ||||||||||||||||||||||
status = fields.Selection( | ||||||||||||||||||||||
[("accepted", "Accepted"), ("refused", "Refused")], | ||||||||||||||||||||||
string="Status", | ||||||||||||||||||||||
copy=False, | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||||||||||||||||||||||
property_id = fields.Many2one( | ||||||||||||||||||||||
"estate_property", string="Property", ondelete="cascade" | ||||||||||||||||||||||
) | ||||||||||||||||||||||
validity = fields.Integer(string="Validity (days)", default=7) | ||||||||||||||||||||||
deadline_date = fields.Date( | ||||||||||||||||||||||
string="Deadline", compute="_compute_deadline", inverse="_inverse_deadline" | ||||||||||||||||||||||
) | ||||||||||||||||||||||
property_type_id = fields.Many2one( | ||||||||||||||||||||||
related="property_id.property_type_id", store=True | ||||||||||||||||||||||
) | ||||||||||||||||||||||
|
||||||||||||||||||||||
@api.depends("property_id.create_date", "validity") | ||||||||||||||||||||||
def _compute_deadline(self): | ||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||
if record.property_id.create_date: | ||||||||||||||||||||||
record.deadline_date = record.property_id.create_date + relativedelta( | ||||||||||||||||||||||
days=record.validity | ||||||||||||||||||||||
) | ||||||||||||||||||||||
else: | ||||||||||||||||||||||
record.deadline_date = False | ||||||||||||||||||||||
Comment on lines
+32
to
+37
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
|
||||||||||||||||||||||
|
||||||||||||||||||||||
def _inverse_deadline(self): | ||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||
if record.property_id.create_date and record.deadline_date: | ||||||||||||||||||||||
flag = fields.Date.from_string(record.deadline_date) | ||||||||||||||||||||||
flag1 = fields.Date.from_string(record.property_id.create_date) | ||||||||||||||||||||||
if flag and flag1: | ||||||||||||||||||||||
record.validity = (flag - flag1).days | ||||||||||||||||||||||
else: | ||||||||||||||||||||||
record.validity = 7 | ||||||||||||||||||||||
else: | ||||||||||||||||||||||
record.validity = 7 | ||||||||||||||||||||||
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
I think we can simplify it But make sure to check all the cases working fine. |
||||||||||||||||||||||
|
||||||||||||||||||||||
def action_refused(self): | ||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||
record.status = "refused" | ||||||||||||||||||||||
|
||||||||||||||||||||||
def action_accepted(self): | ||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||
record.status = "accepted" | ||||||||||||||||||||||
record.property_id.selling_price = record.price | ||||||||||||||||||||||
record.property_id.buyer_id = record.partner_id | ||||||||||||||||||||||
|
||||||||||||||||||||||
@api.model | ||||||||||||||||||||||
def create(self, vals): | ||||||||||||||||||||||
property_id = vals.get("property_id") | ||||||||||||||||||||||
property_users = self.env["estate_property"].browse(property_id) | ||||||||||||||||||||||
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
|
||||||||||||||||||||||
property_users.state = "offer received" | ||||||||||||||||||||||
if property_users.offer_ids.filtered(lambda o: o.price >= vals.get("price")): | ||||||||||||||||||||||
raise UserError( | ||||||||||||||||||||||
"You cannot create an offer with a lower amount than an existing offer." | ||||||||||||||||||||||
) | ||||||||||||||||||||||
return super().create(vals) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class PropertyTag(models.Model): | ||
_name = "real.estate.property.tag" | ||
_description = "Property Type" | ||
_order = "name" | ||
|
||
name = fields.Char(required=True) | ||
color = fields.Integer() | ||
|
||
_sql_constraints = [("name", "UNIQUE(name)", "A property tag name must be unique")] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from odoo import api, models, fields | ||
|
||
|
||
class PropertyType(models.Model): | ||
_name = "real.estate.property.type" | ||
_description = "Property Type" | ||
_order = "name" | ||
|
||
name = fields.Char(string="Property Type", required=True) | ||
description = fields.Text(string="Description") | ||
|
||
property_ids = fields.One2many( | ||
"estate_property", "property_type_id", string="Offers" | ||
) | ||
# nickname = fields.Char(related='property_ids.partner_id.name', store=True) | ||
offer_ids = fields.One2many("estate.property.offer", "property_type_id", store=True) | ||
sequence = fields.Integer("Sequence") | ||
offer_count = fields.Integer(compute="_compute_offer_count") | ||
_sql_constraints = [("name", "UNIQUE(name)", "A 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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class MyModel(models.Model): | ||
_name = "test_model" | ||
_description = "My New Model" | ||
|
||
name = fields.Char(string="Name", required=True) | ||
description = fields.Text() | ||
address = fields.Text() |
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_property","access_estate_property","model_estate_property","base.group_user",1,1,1,1 | ||
estate.access_real_estate_property_type,access_real_estate_property_type,estate.model_real_estate_property_type,base.group_user,1,1,1,1 | ||
estate.access_real_estate_property_tag,access_real_estate_property_tag,estate.model_real_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,50 @@ | ||||||||||||||||||
<odoo> | ||||||||||||||||||
<record id="estate_property_offer_action" model="ir.actions.act_window"> | ||||||||||||||||||
<field name="name"> | ||||||||||||||||||
Offer | ||||||||||||||||||
</field> | ||||||||||||||||||
<field name="res_model">estate.property.offer</field> | ||||||||||||||||||
<field name="view_mode"> | ||||||||||||||||||
tree,form | ||||||||||||||||||
</field> | ||||||||||||||||||
<field name="domain"> | ||||||||||||||||||
[('property_type_id', '=', active_id)] | ||||||||||||||||||
</field> | ||||||||||||||||||
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
Check in other files also |
||||||||||||||||||
</record> | ||||||||||||||||||
<record id="view_estate_property_offer_tree" model="ir.ui.view"> | ||||||||||||||||||
<field name="name"> | ||||||||||||||||||
estate.property.offer.tree | ||||||||||||||||||
</field> | ||||||||||||||||||
<field name="model">estate.property.offer</field> | ||||||||||||||||||
<field name="arch" type="xml"> | ||||||||||||||||||
<tree editable="bottom" decoration-bf="status=='accepted'" decoration-success="status=='accepted'" decoration-danger="status=='refused'"> | ||||||||||||||||||
<field name="price" /> | ||||||||||||||||||
<field name="partner_id" /> | ||||||||||||||||||
<field name="validity" /> | ||||||||||||||||||
<field name="deadline_date" /> | ||||||||||||||||||
<button name="action_accepted" type="object" icon="fa-check" title="done" invisible="status in ('accepted', 'refused')" /> | ||||||||||||||||||
<button name="action_refused" type="object" icon="fa-times" title="cancel" invisible="status in ('accepted', 'refused')" /> | ||||||||||||||||||
<field name="status" column_invisible="1" /> | ||||||||||||||||||
</tree> | ||||||||||||||||||
</field> | ||||||||||||||||||
</record> | ||||||||||||||||||
<record id="view_estate_property_offer_form" model="ir.ui.view"> | ||||||||||||||||||
<field name="name"> | ||||||||||||||||||
estate.property.offer.form | ||||||||||||||||||
</field> | ||||||||||||||||||
<field name="model">estate.property.offer</field> | ||||||||||||||||||
<field name="arch" type="xml"> | ||||||||||||||||||
<form string="Properties Form"> | ||||||||||||||||||
<sheet> | ||||||||||||||||||
<group> | ||||||||||||||||||
<field name="price" /> | ||||||||||||||||||
<field name="partner_id" /> | ||||||||||||||||||
<field name="validity" /> | ||||||||||||||||||
<field name="deadline_date" /> | ||||||||||||||||||
<field name="status" /> | ||||||||||||||||||
</group> | ||||||||||||||||||
</sheet> | ||||||||||||||||||
</form> | ||||||||||||||||||
</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.
Modify the model name as per the convention.