-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[ADD] estate: a new mdoule to manage estate properties
Problem --------- Users are missing a way to manage real estate properties in odoo. Objective --------- - Add a way to create new properties, track their attributes and the state of the sale (new, offer received, ..., sold). - Add a way to visualise and filter the properties base on their attributes and status. And give the user a way to categorize the properties. - Add a way to manage offer made by parters on individual properties. Be able to accept/refuse the offers. - Create an invoice to the client once a sale is closed. Solution --------- Add a new module `estate`. This model is in charge of managing the properties, tracking their sales state and offers. Add a new module `estate_account` to create invoices once a sale is closed. Closes #162
- Loading branch information
Showing
21 changed files
with
618 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,3 +127,6 @@ dmypy.json | |
|
||
# Pyre type checker | ||
.pyre/ | ||
|
||
# vscode stuff | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
'name': "Real Estate", | ||
'version': '0.1', | ||
'depends': ['base'], | ||
'description': "A real estate app", | ||
'category': 'Tutorials/RealEstate', | ||
'application': True, | ||
# data files always loaded at installation | ||
'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/res_users_views.xml", | ||
"views/estate_menus.xml" | ||
], | ||
'license': "LGPL-3", | ||
'demo': [ | ||
"data/demo.xml", | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<odoo> | ||
<data> | ||
<record id="model_estate_property_action_cancel" model="ir.actions.server"> | ||
<field name="name">Mass cancel</field> | ||
<field name="model_id" ref="estate.model_estate_property"/> | ||
<field name="binding_model_id" ref="estate.model_estate_property"/> | ||
<field name="binding_view_types">list</field> | ||
<field name="state">code</field> | ||
<field name="code">action = records.action_cancel()</field> | ||
</record> | ||
</data> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 res_users |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
from dateutil.relativedelta import relativedelta | ||
from odoo import api, fields, models, _ | ||
from odoo.exceptions import UserError, ValidationError | ||
from odoo.tools.float_utils import float_compare, float_is_zero | ||
|
||
|
||
class EstateProperty(models.Model): | ||
_name = 'estate.property' | ||
_description = "Real Estate property" | ||
|
||
_sql_constraints = [ | ||
('check_posiive_expected_price', 'CHECK(expected_price > 0)', "The expected price must be strictly positive."), | ||
('check_positive_selling_price', 'CHECK(selling_price > 0)', "The selling price must be strictly positive."), | ||
] | ||
_order = "id desc" | ||
|
||
name = fields.Char("Title", required=True) | ||
description = fields.Text("Description") | ||
postcode = fields.Char("Postcode") | ||
date_availability = fields.Date( | ||
"Available From", | ||
copy=False, | ||
default=lambda self: fields.Date.today() + relativedelta(months=3), | ||
) | ||
expected_price = fields.Float("Expected Price", required=True) | ||
selling_price = fields.Float("Selling Price", readonly=True) | ||
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( | ||
string="Garden Orientation", | ||
selection=[('N', "North"), ('S', "South"), ('E', "East"), ('W', "West")], | ||
) | ||
active = fields.Boolean("Active", default=True) | ||
state = fields.Selection( | ||
string="Status", | ||
selection=[ | ||
('new', "New"), | ||
('offer_received', "Offer Received"), | ||
('offer_accepted', "Offer Accepted"), | ||
('sold', "Sold"), | ||
('canceled', "Canceled"), | ||
], | ||
required=True, | ||
copy=False, | ||
default='new', | ||
) | ||
property_type_id = fields.Many2one('estate.property.type', string="Property Type") | ||
buyer_id = fields.Many2one('res.partner', string="Buyer", copy=False) | ||
salesman_id = fields.Many2one('res.users', string="Salesman", default=lambda self: self.env.user) | ||
tag_ids = fields.Many2many('estate.property.tag', string="Tags") | ||
offer_ids = fields.One2many('estate.property.offer', "property_id", string="Offers") | ||
total_area = fields.Integer(string="Total Area (sqm)", compute='_compute_total_area') | ||
best_price = fields.Integer(string="Best Offer", compute='_compute_best_price') | ||
|
||
@api.depends('offer_ids.price') | ||
def _compute_best_price(self): | ||
for estate in self: | ||
estate.best_price = max(estate.offer_ids.mapped('price'), default=False) | ||
|
||
@api.depends('living_area', 'garden_area') | ||
def _compute_total_area(self): | ||
for estate in self: | ||
estate.total_area = estate.living_area + estate.garden_area | ||
|
||
@api.onchange('garden') | ||
def _onchange_garden(self): | ||
if self.garden: | ||
self.garden_area = 10 | ||
self.garden_orientation = 'N' | ||
else: | ||
self.garden_area = 0 | ||
self.garden_orientation = '' | ||
|
||
def action_sold(self): | ||
self.ensure_one() | ||
if self.state == 'canceled': | ||
raise UserError(_("Canceled properties cannot be sold.")) | ||
self.state = 'sold' | ||
return True | ||
|
||
def action_cancel(self): | ||
self.ensure_one() | ||
if self.state == 'sold': | ||
raise UserError(_("Sold properties cannot be canceled.")) | ||
self.state = 'canceled' | ||
return True | ||
|
||
@api.constrains('selling_price', 'expected_price') | ||
def _check_selling_price(self): | ||
for estate in self: | ||
if ( | ||
not float_is_zero(estate.selling_price, 3) | ||
and float_compare(estate.selling_price, estate.expected_price * 0.9, 3) == -1 | ||
): | ||
raise ValidationError(_("The selling price cannot be lower than 90% of the expected price.")) | ||
|
||
@api.ondelete(at_uninstall=False) | ||
def _unlink_expecpt_new_and_sold_properties(self): | ||
if any(estate.state in ('new', 'sold') for estate in self): | ||
raise UserError(_("Can't delete new or sold properties.")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
from datetime import timedelta | ||
from odoo import _, api, fields, models | ||
from odoo.exceptions import UserError | ||
|
||
|
||
class PropertyOffer(models.Model): | ||
_name = "estate.property.offer" | ||
_description = "Property offer" | ||
|
||
_sql_constraints = [ | ||
( | ||
"check_positive_offer_price", | ||
"CHECK(price > 0)", | ||
"The offer price must be strictly positive.", | ||
), | ||
] | ||
_order = "price desc" | ||
|
||
price = fields.Float(string="Price") | ||
status = fields.Selection( | ||
string="Status", | ||
selection=[("accepted", "Accepted"), ("refused", "Refused")], | ||
copy=False, | ||
) | ||
partner_id = fields.Many2one("res.partner", string="Partner", required=True) | ||
property_id = fields.Many2one("estate.property", string="Property", required=True) | ||
validity = fields.Integer(string="Validiy (days)", default=7) | ||
date_deadline = fields.Date( | ||
string="Deadline Date", | ||
compute="_compute_date_deadline", | ||
inverse="_inverse_date_deadline", | ||
) | ||
property_type_id = fields.Many2one( | ||
"estate.property.type", | ||
string="Property Type", | ||
related="property_id.property_type_id", | ||
) | ||
|
||
@api.depends("create_date", "validity") | ||
def _compute_date_deadline(self): | ||
for offer in self: | ||
create_date = offer.create_date or fields.Datetime.now() | ||
offer.date_deadline = create_date.date() + timedelta(days=offer.validity) | ||
|
||
def _inverse_date_deadline(self): | ||
for offer in self: | ||
create_date = offer.create_date or fields.Datetime.now() | ||
offer.validity = (offer.date_deadline - create_date.date()).days | ||
|
||
def action_accept_offer(self): | ||
for offer in self: | ||
if offer.status == "accepted": | ||
raise UserError(_("You already accepted the offer.")) | ||
if offer.property_id.buyer_id: | ||
raise UserError(_("Only one offer can be accepted.")) | ||
offer.status = "accepted" | ||
offer.property_id.selling_price = offer.price | ||
offer.property_id.buyer_id = offer.partner_id | ||
for property_offer in offer.property_id.offer_ids: | ||
if property_offer.id != offer.id: | ||
offer.status = "refused" | ||
return True | ||
|
||
def action_refuse_offer(self): | ||
for offer in self: | ||
if offer.status == "accepted": | ||
raise UserError( | ||
_("You cannot refuse an offer once it has been accepted.") | ||
) | ||
offer.status = "refused" | ||
|
||
@api.model_create_multi | ||
def create(self, vals_list): | ||
for vals in vals_list: | ||
property_id = self.env["estate.property"].browse(vals["property_id"]) | ||
price = vals["price"] | ||
if any(prev_offer.price > price for prev_offer in property_id.offer_ids): | ||
raise UserError(_("An offer with an higher price already exists.")) | ||
property_id.state = "offer_received" | ||
return super().create(vals_list) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from random import randint | ||
from odoo import models, fields | ||
|
||
|
||
class PropertyTag(models.Model): | ||
_name = 'estate.property.tag' | ||
_description = "Property tag" | ||
|
||
_sql_constraints = [ | ||
('check_unique_name', 'UNIQUE(name)', "This tag name already exists."), | ||
] | ||
_order = "name asc" | ||
|
||
name = fields.Char("Name", required=True) | ||
color = fields.Integer("Color", default=lambda _: randint(1, 10)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class PropertyType(models.Model): | ||
_name = 'estate.property.type' | ||
_description = "Property type" | ||
|
||
_sql_constraints = [ | ||
('check_unique_name', 'UNIQUE(name)', "This type name already exists."), | ||
] | ||
_order = "name asc" | ||
|
||
name = fields.Char("Name", required=True) | ||
sequence = fields.Integer("Sequence") | ||
offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string="Linked Offers") | ||
offer_count = fields.Integer("Nb. Linked Offer", compute='_compute_offer_count') | ||
|
||
@api.depends('offer_ids') | ||
def _compute_offer_count(self): | ||
for property_type in self: | ||
property_type.offer_count = len(property_type.offer_ids) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from odoo import models, fields | ||
|
||
|
||
class ResUsers(models.Model): | ||
# _name = 'res.users' | ||
_inherit = 'res.users' | ||
|
||
property_ids = fields.One2many("estate.property", "salesman_id", string="Properties", domain=['|', ('state', '=', 'new'), ('state', '=', 'offer_received')]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_user,1,1,1,1 | ||
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_user,1,1,1,1 | ||
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_user,1,1,1,1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<menuitem id="estate_property_menu" name="Real Estate"> | ||
<menuitem id="estate_property_menu_advertisments" name="Advertisements"> | ||
<menuitem id="estate_property_menu_properties" name="Properties" action="estate.estate_property_action"/> | ||
</menuitem> | ||
<menuitem id="estate_property_menu_settings" name="Settings"> | ||
<menuitem id="estate_property_type_menu" name="Property Types" action="estate.estate_property_type_action"/> | ||
<menuitem id="estate_property_tag_menu" name="Property Tags" action="estate.estate_property_tag_action"/> | ||
</menuitem> | ||
</menuitem> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_offer_action" model="ir.actions.act_window"> | ||
<field name="name">Real Estate Property Offer</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_view_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 string="Offers" editable="bottom" | ||
decoration-success="status == 'accepted'" | ||
decoration-danger="status == 'refused'" | ||
> | ||
<field name="price"/> | ||
<field name="partner_id"/> | ||
<field name="validity"/> | ||
<field name="date_deadline"/> | ||
<button name="action_accept_offer" type="object" string="Accept" icon="fa-check" invisible="status"/> | ||
<button name="action_refuse_offer" type="object" string="Refuse" icon="fa-times" invisible="status"/> | ||
<field name="status"/> | ||
<field name="property_type_id"/> | ||
</list> | ||
</field> | ||
</record> | ||
|
||
<record id="estate_property_offer_view_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="Offer"> | ||
<sheet> | ||
<group> | ||
<field name="price"/> | ||
<field name="partner_id"/> | ||
<field name="status"/> | ||
<field name="validity"/> | ||
<field name="date_deadline"/> | ||
</group> | ||
</sheet> | ||
</form> | ||
</field> | ||
</record> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?xml version="1.0"?> | ||
<odoo> | ||
<record id="estate_property_tag_action" model="ir.actions.act_window"> | ||
<field name="name">Real Estate Property Tag</field> | ||
<field name="res_model">estate.property.tag</field> | ||
<field name="view_mode">list,form</field> | ||
</record> | ||
|
||
<record id="estate_property_tag_view_form" model="ir.ui.view"> | ||
<field name="name">estate.property.tag.form</field> | ||
<field name="model">estate.property.tag</field> | ||
<field name="arch" type="xml"> | ||
<form string="Property Tag"> | ||
<sheet> | ||
<group> | ||
<field name="name"/> | ||
<field name="color" required="True" widget="color_picker"/> | ||
</group> | ||
</sheet> | ||
</form> | ||
</field> | ||
</record> | ||
|
||
<record id="estate_property_tag_view_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 string="Property Tags" editable="bottom"> | ||
<field name="name"/> | ||
<field name="color" widget="color_picker"/> | ||
</list> | ||
</field> | ||
</record> | ||
</odoo> |
Oops, something went wrong.