-
Notifications
You must be signed in to change notification settings - Fork 2.3k
[ADD] estate: added a new Estate Module #844
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
base: 18.0
Are you sure you want to change the base?
Changes from all commits
0f35aaa
9920ca2
3021c7b
aba5754
75c66b6
5cc2dad
fe2ca9a
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,3 @@ | ||
{ | ||
"python.languageServer": "None" | ||
} |
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,24 @@ | ||
{ | ||
'name': "Estate", | ||
'version': '1.0', | ||
'license': 'LGPL-3', | ||
'depends': ['base'], | ||
'author': "Kalpan Desai", | ||
'category': 'Estate/sales', | ||
'description': """ | ||
Module specifically designed for real estate buisness case. | ||
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. typo with "business". |
||
""", | ||
'installable': True, | ||
'application': True, | ||
|
||
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. Unnecessary empty line here. Also you can add "license" key. |
||
'data': [ | ||
'security/ir.model.access.csv', | ||
'views/estate_property_views.xml', | ||
'views/estate_property_offer_views.xml', | ||
'views/estate_property_type_views.xml', | ||
'views/estate_property_tag_views.xml', | ||
'views/estate_res_user_views.xml', | ||
'views/estate_menus.xml', | ||
] | ||
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. indentation. |
||
|
||
} |
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_res_user |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,97 @@ | ||||||
from odoo import models, fields, api | ||||||
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. sort it alphabetically. |
||||||
from dateutil.relativedelta import relativedelta | ||||||
from odoo.exceptions import UserError, ValidationError | ||||||
from odoo.tools import float_utils, _ | ||||||
|
||||||
|
||||||
class EstateProperty(models.Model): | ||||||
_name = "estate.property" | ||||||
_description = "Details of all the properties will be stored over here." | ||||||
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.
|
||||||
_order = "id desc" | ||||||
name = fields.Char('Name', required=True, default='Unknown Property') | ||||||
description = fields.Text('Description') | ||||||
postcode = fields.Char('Postcode') | ||||||
date_availability = fields.Date( | ||||||
"Date Availability", | ||||||
default=lambda self: fields.Date.to_string(fields.Date.context_today(self) + relativedelta(months=3)) | ||||||
) | ||||||
expected_price = fields.Float('Expected Price', required=True) | ||||||
selling_price = fields.Float('Selling Price', readonly=True, copy=False) | ||||||
bedrooms = fields.Integer('Bedrooms', default=2) | ||||||
living_area = fields.Integer('Living Area (sqm)') | ||||||
facades = fields.Integer('Facades') | ||||||
garage = fields.Boolean('Garage') | ||||||
garden = fields.Boolean('Garden') | ||||||
garden_area = fields.Integer('Garden Area (sqm)') | ||||||
garden_orientation = fields.Selection( | ||||||
[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')], | ||||||
string='Garden Orientation' | ||||||
) | ||||||
active = fields.Boolean('Active', default=True) | ||||||
state = fields.Selection( | ||||||
[('new', 'New'), ('offer_received', 'Offer Received'), ('offer_accepted', 'Offer Accepted'), | ||||||
('sold', 'Sold'), ('canceled', 'Canceled')], | ||||||
string='Status', default='new', required=True, copy=False | ||||||
) | ||||||
property_type_id = fields.Many2one("estate.property.type", string="Property Type", required=True) | ||||||
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False) | ||||||
salesperson_id = fields.Many2one("res.users", string="Salesperson", default=lambda self: self.env.user) | ||||||
tags_ids = fields.Many2many("estate.property.tag", string="Tags") | ||||||
offer_ids = fields.One2many("estate.property.offer", "property_id", string="Offers") | ||||||
total_area = fields.Float(compute="_compute_total_area", string="Total Area", readonly=True) | ||||||
best_price = fields.Float(compute="_compute_best_price", string="Best Price", readonly=True) | ||||||
|
||||||
_sql_constraints = [ | ||||||
('check_expected_price', 'CHECK(expected_price > 0)', 'The expected price must strictly Positive.'), | ||||||
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_selling_price', 'CHECK(selling_price > 0)', 'The selling price must be positive.'), | ||||||
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. Different sentence, why not strictly here ^^'. |
||||||
] | ||||||
|
||||||
@api.depends("garden_area", "living_area") | ||||||
def _compute_total_area(self): | ||||||
for record in self: | ||||||
record.total_area = record.garden_area + record.living_area | ||||||
|
||||||
@api.depends("offer_ids.price") | ||||||
def _compute_best_price(self): | ||||||
for record in self: | ||||||
prices = record.offer_ids.mapped("price") | ||||||
record.best_price = max(prices, default=0) | ||||||
|
||||||
@api.onchange("garden") | ||||||
def _onchange_garden(self): | ||||||
if self.garden: | ||||||
self.garden_area = 10 | ||||||
self.garden_orientation = "north" | ||||||
else: | ||||||
self.garden_area = 0 | ||||||
self.garden_orientation = False | ||||||
|
||||||
def action_sold(self): | ||||||
if self.state != "canceled": | ||||||
self.state = "sold" | ||||||
# print("Log by KDES", flush=True) | ||||||
else: | ||||||
raise UserError("Canceled properties can't be sold") | ||||||
return True | ||||||
|
||||||
def action_cancel(self): | ||||||
for record in self: | ||||||
if record.state != "sold": | ||||||
record.state = "canceled" | ||||||
else: | ||||||
raise UserError("Sold properties can't be sold") | ||||||
return True | ||||||
|
||||||
@api.constrains('selling_price') | ||||||
def _check_price(self): | ||||||
for record in self: | ||||||
if not record.selling_price: | ||||||
continue | ||||||
|
||||||
if float_utils.float_compare(record.selling_price, record.expected_price * 0.9, precision_rounding=3) == -1: | ||||||
raise ValidationError(_('The selling cannot be lower than 90% of the expected price.')) | ||||||
|
||||||
@api.ondelete(at_uninstall=False) | ||||||
def _unlink_if_state_check(self): | ||||||
if any(record.state not in ('new', 'canceled') for record in self): | ||||||
raise UserError(_("You cannot delete a property that is not in the 'New' or 'Canceled' state.")) |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,65 @@ | ||||||||||||||||||||||||
from odoo import api, fields, models, _ | ||||||||||||||||||||||||
from odoo.exceptions import UserError | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
class EstatePropertyOffer(models.Model): | ||||||||||||||||||||||||
_name = "estate.property.offer" | ||||||||||||||||||||||||
_description = "Property Offer" | ||||||||||||||||||||||||
_order = "price desc" | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
price = fields.Float() | ||||||||||||||||||||||||
status = fields.Selection( | ||||||||||||||||||||||||
string="Type", | ||||||||||||||||||||||||
selection=[("accepted", "Accepted"), ("refused", "Refused")], | ||||||||||||||||||||||||
copy=False, | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
partner_id = fields.Many2one("res.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("estate.property.type", related="property_id.property_type_id", store=True) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
_sql_constraints = [ | ||||||||||||||||||||||||
("check_price", "CHECK(price > 0)", "The price must be strictly positive.") | ||||||||||||||||||||||||
] | ||||||||||||||||||||||||
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. linting. |
||||||||||||||||||||||||
|
||||||||||||||||||||||||
@api.depends("validity") | ||||||||||||||||||||||||
def _compute_date_deadline(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
if not record.create_date: | ||||||||||||||||||||||||
today = fields.Datetime.today() | ||||||||||||||||||||||||
record.date_deadline = fields.Datetime.add(today, days=record.validity) | ||||||||||||||||||||||||
else: | ||||||||||||||||||||||||
record.date_deadline = fields.Datetime.add( | ||||||||||||||||||||||||
record.create_date, days=record.validity | ||||||||||||||||||||||||
) | ||||||||||||||||||||||||
Comment on lines
+29
to
+36
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. better to use
Suggested change
|
||||||||||||||||||||||||
|
||||||||||||||||||||||||
def _inverse_date_deadline(self): | ||||||||||||||||||||||||
for record in self: | ||||||||||||||||||||||||
record.validity = (record.date_deadline - record.create_date.date()).days | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def action_accept_offer(self): | ||||||||||||||||||||||||
if self.property_id.state in {'accepted', 'sold'}: | ||||||||||||||||||||||||
raise UserError(_('An offer as already been accepted.')) | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
self.status = "accepted" | ||||||||||||||||||||||||
self.property_id.selling_price = self.price | ||||||||||||||||||||||||
self.property_id.buyer_id = self.partner_id | ||||||||||||||||||||||||
self.property_id.state = "offer_accepted" | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
return True | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
def action_refuse_offer(self): | ||||||||||||||||||||||||
self.ensure_one() | ||||||||||||||||||||||||
self.status = "refused" | ||||||||||||||||||||||||
return True | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
@api.model_create_multi | ||||||||||||||||||||||||
def create(self, vals_list): | ||||||||||||||||||||||||
for vals in vals_list: | ||||||||||||||||||||||||
property = self.env["estate.property"].browse(vals["property_id"]) | ||||||||||||||||||||||||
property.state = "offer_received" | ||||||||||||||||||||||||
if property.best_price > vals["price"]: | ||||||||||||||||||||||||
raise UserError("The offer must be higher than the existing offer") | ||||||||||||||||||||||||
return super().create(vals_list) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class EstatePropertyTag(models.Model): | ||
_name = "estate.property.tag" | ||
_description = "Property Tags" | ||
_order = "name" | ||
|
||
name = fields.Char() | ||
color = fields.Integer("Color Index", default=0, help="Color index for the tag") | ||
|
||
_sql_constraints = [ | ||
('check_name', 'UNIQUE(name)', 'A tag must be unique.'), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
from odoo import models, fields, api | ||
|
||
|
||
class EstatePropertyType(models.Model): | ||
_name = "estate.property.type" | ||
_description = "Types of properties available in the estate module." | ||
_order = "name" | ||
|
||
name = fields.Char('Name', required=True) | ||
property_ids = fields.One2many("estate.property", "property_type_id", string="Property") | ||
sequence = fields.Integer("Sequence", default=1, help="Used to order types. Lower is better.") | ||
offer_ids = fields.One2many("estate.property.offer", "property_type_id", string="Offers") | ||
offer_count = fields.Integer(compute="compute_offer_count", string="Offers Count", store=True, readonly=True) | ||
|
||
_sql_constraints = [ | ||
('check_proterty_type_name', 'UNIQUE(name)', 'A Type 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,8 @@ | ||
from odoo import fields, models | ||
|
||
|
||
class User(models.Model): | ||
_inherit = "res.users" | ||
|
||
property_ids = fields.One2many("estate.property", inverse_name="salesperson_id") | ||
domain = ["|", ("state", "=", "new"), ("state", "=", "offer_received")] |
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_base_group,estate.property.basegroup,model_estate_property,base.group_user,1,1,1,1 | ||
access_estate_property_type_base_group,estate.property.type.basegroup,model_estate_property_type,base.group_user,1,1,1,1 | ||
access_estate_property_tag_base_group,estate.property.tag.basegroup,model_estate_property_tag,base.group_user,1,1,1,1 | ||
access_estate_property_offer_base_group,estate.property.offer.basegroup,model_estate_property_offer,base.group_user,1,1,1,1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<odoo> | ||
<menuitem id="menu_estate_root" name="Real Estate"/> | ||
|
||
<!-- Advertisement MENU --> | ||
<menuitem id="menu_estate_advertisements" | ||
name="Advertisements" | ||
parent="menu_estate_root"/> | ||
|
||
<menuitem id="menu_estate_property" | ||
name="Properties" | ||
parent="menu_estate_advertisements" | ||
action="action_estate_property"/> | ||
|
||
<!-- Settings MENU --> | ||
<menuitem id="menu_estate_settings" | ||
name="Settings" | ||
parent="menu_estate_root"/> | ||
|
||
<menuitem id="menu_estate_property_type" | ||
name="Properties Type" | ||
parent="menu_estate_settings" | ||
action="action_estate_property_type"/> | ||
|
||
<menuitem id="menu_estate_property_tag" | ||
name="Properties Tags" | ||
parent="menu_estate_settings" | ||
action="action_estate_property_tag"/> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_offer_action" model="ir.actions.act_window"> | ||
<field name="name">Property Offers</field> | ||
<field name="res_model">estate.property.offer</field> | ||
<field name="view_mode">list,form</field> | ||
<field name="domain">[('property_type_id', '=', active_id)]</field> | ||
</record> | ||
|
||
<record id="estate_property_offer_list" model="ir.ui.view"> | ||
<field name="name">estate.property.offer.list</field> | ||
<field name="model">estate.property.offer</field> | ||
<field name="arch" type="xml"> | ||
<list editable="bottom" decoration-success="status=='accepted'" decoration-danger="status=='refused'"> | ||
<field name="price" string="Price"/> | ||
<field name="partner_id" string="Partner"/> | ||
<field name="validity" string="Validity"/> | ||
<field name="date_deadline" string="Deadline"/> | ||
<!-- <field name="property_type_id" string="property ,"/> --> | ||
<button name="action_accept_offer" type="object" icon="fa-check" title="Accept" invisible="status"/> | ||
<button name="action_refuse_offer" type="object" icon="fa-times" title="Refuse" invisible="status"/> | ||
<field name="status" string="Status"/> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<record id="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> | ||
<sheet> | ||
<group> | ||
<field name="price" width="40px" string="Price"/> | ||
<field name="partner_id" width="40px" string="Partner"/> | ||
<field name="validity" width="40px" string="Validity"/> | ||
<field name="date_deadline" width="40px" string="Deadline"/> | ||
<field name="status" width="40px" string="Status"/> | ||
</group> | ||
</sheet> | ||
</form> | ||
</field> | ||
</record> | ||
</odoo> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="action_estate_property_tag" model="ir.actions.act_window"> | ||
<field name="name">Tags</field> | ||
<field name="res_model">estate.property.tag</field> | ||
<field name="view_mode">list,form</field> | ||
</record> | ||
|
||
<record id="estate_property_tag_list" model="ir.ui.view"> | ||
<field name="name">estate.property.tag.list</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="arch" type="xml"> | ||
<list editable="bottom"> | ||
<field name="name" string="Title"/> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<record id="estate_property_tag_form" model="ir.ui.view"> | ||
<field name="name">estate.property.form</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="arch" type="xml"> | ||
<form> | ||
<sheet> | ||
<group> | ||
<field name="name" placeholder="e.g. cozy" string="Name"/> | ||
</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.
Now you add license in commit 5 better to add it in first commit.