Skip to content

[ADD] Estate: Adding the new Real Estate Module's directory. #778

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 14 commits into
base: 18.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions estate/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import models
24 changes: 24 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
'name': 'Estate',
'version': '1.0',
'category': 'Real Estate',
'sequence': 15,
'summary': 'Track Real Estate',
'description': "This app is made for real estate tracking.",
'website': 'https://www.odoo.com/page/estate',
'depends': [
'base'
],
'data': [
'security/ir.model.access.csv',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_views.xml',
'views/res_users_views.xml',
'views/estate_menus.xml'
],
'installable': True,
'application': True,
'license': 'LGPL-3'
}
1 change: 1 addition & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import estate_property, estate_property_offer, estate_property_tag, estate_property_type, res_users
104 changes: 104 additions & 0 deletions estate/models/estate_property.py
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"
_sql_constraints = [
('check_expected_price', 'CHECK(expected_price > 0)', 'Expected price must be strictly positve'),
('check_selling_price', 'CHECK(selling_price >= 0)', 'Selling price must be positive')
]
_order = 'id desc'

name = fields.Char('Name', required=True)
description = fields.Char('Description')
post_code = fields.Char('Post Code')
state = fields.Selection(
string="State",
selection=[
('new', 'New'),
('offer received', 'Offer Received'),
('offer accepted', 'Offer Accepted'),
('sold', 'Sold'),
('canceled', 'Canceled')],
help="Estate current state",
required=True,
default='new',
copy=False
)
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, copy=False)
best_price = fields.Float('Best Price', compute='_compute_best_price')
bedrooms = fields.Integer('Bedrooms Count', default=2)
living_area = fields.Float('Living Area size')
facades = fields.Integer('Number of facades')
garage = fields.Boolean('Has garage')
garden = fields.Boolean('Has garden')
garden_area = fields.Float('Garden area')
garden_orientation = fields.Selection(
string='Garden Orientation',
selection=[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')],
help="Garden orientation selection"
)
total_area = fields.Float('Total Area', compute='_compute_total_area')
active = fields.Boolean('Is Active', default=True)
property_type_id = fields.Many2one('estate.property.type', string='Property Type')
buyer_id = fields.Many2one('res.partner', 'Buyer', copy=False)
salesperson_id = fields.Many2one('res.users', 'Salesperson', index=True, 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')

@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')
def _compute_best_price(self):
for record in self:
prices = record.offer_ids.mapped('price')
record.best_price = max(prices) if prices else 0

@api.onchange('garden')
def _onchange_garden_checkbox(self):
self.garden_area = 10 if self.garden else 0
self.garden_orientation = 'north' if self.garden else None

@api.constrains('selling_price', 'expected_price')
def _check_selling_price(self):
for record in self:
if float_is_zero(record.selling_price, precision_digits=2):
continue

if float_compare(record.selling_price, record.expected_price * 0.9, precision_digits=2) < 0:
raise ValidationError("Selling price cannot be lower than 90% of the expected price.")

@api.ondelete(at_uninstall=False)
def _unlink_if_state_is_new_or_canceled(self):
invalid_records = self.filtered(lambda r: r.state not in ('new', 'canceled'))
if invalid_records:
raise UserError("You can only delete properties in NEW or CANCELED state.")

def action_set_sold(self):
for record in self:
if (record.state == 'sold'):
raise UserError('Property Already Sold')
if record.state != 'canceled':
record.state = 'sold'
else:
raise UserError("Cannot sell a canceled property")
return True

def action_set_cancel(self):
for record in self:
if record.state == 'sold':
raise UserError('Cannot cancel a sold property')
if (record.state == 'canceled'):
raise UserError("Property Already canceled")
record.state = 'canceled'
return True
72 changes: 72 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
from datetime import timedelta

from odoo import api, fields, models
from odoo.exceptions import UserError


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer"
_sql_constraints = [
('check_offer_price', 'CHECK(price >= 0)', '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
)
validity = fields.Integer(string="Validity (days)", default=7)
date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True)
partner_id = fields.Many2one('res.partner', required=True, string="Partner")
property_id = fields.Many2one('estate.property', required=True, string="Property")
property_type_id = fields.Many2one(related='property_id.property_type_id', store=True, string='Property Type')

@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 if offer.date_deadline else 0

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
property = self.env['estate.property'].browse(vals['property_id'])
if property.state == 'new':
property.state = 'offer received'
if any(offer.price >= vals['price'] for offer in property.offer_ids):
raise UserError("Cannot create offer: there is already an offer with an equal or higher price.")
return super().create(vals_list)

def action_set_accept(self):
for record in self:
if (record.property_id.state in ('offer accepted', 'sold')):
raise UserError('An offer already accepted for this property')
if (record.property_id.state == 'canceled'):
raise UserError('Property canceled')
if (record.status in ('refused', 'accepted')):
raise UserError('Offer already accepted/refused')

record.property_id.selling_price = record.price
record.property_id.state = 'offer accepted'
record.property_id.buyer_id = record.partner_id
record.status = 'accepted'
return True

def action_set_refuse(self):
for record in self:
if (record.property_id.state in ('offer accepted', 'sold')):
raise UserError('An offer already accepted for this property')
if (record.property_id.state == 'canceled'):
raise UserError('Property canceled')
if (record.status in ('accepted', 'refused')):
raise UserError('Offer already accepted/refused')
record.status = 'refused'
return True
13 changes: 13 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'Estate Property Tags'
_sql_constraints = [
('check_name_unique', 'UNIQUE(name)', "Tag name must be unique")
]
_order = 'name'

name = fields.Char(string='Name', required=True)
color = fields.Integer('Color')
18 changes: 18 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from odoo import api, fields, models


class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = 'Estate Property Type'
_order = 'name'

name = fields.Char(string='Property Type', required=True)
sequence = fields.Integer('Sequence', default=1, help='Used to order offer types. Lower is better.')
property_ids = fields.One2many('estate.property', 'property_type_id', string='Properties')
offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string='Properties offers')
offer_count = fields.Integer(compute='_compute_offer_count', string='Offers Count')

@api.depends('offer_ids')
def _compute_offer_count(self):
for record in self:
record.offer_count = len(record.offer_ids)
7 changes: 7 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from odoo import fields, models


class ResUsers(models.Model):
_inherit = 'res.users'

property_ids = fields.One2many('estate.property', 'salesperson_id', domain=[('state', 'in', ['new', 'offer received'])])
5 changes: 5 additions & 0 deletions estate/security/ir.model.access.csv
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
10 changes: 10 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_property_menu_root" name="Estate">
<menuitem id="estate_property_first_level_menu" name="Estates">
<menuitem id="estate_property_menu_action" action="estate_property_action"/>
<menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</odoo>
57 changes: 57 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_offer_view_search" model="ir.ui.view">
<field name="name">estate.property.offer.search</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<search string="Property offer Search">
<field name="partner_id" string="Partner"/>
<field name="property_id" string="Property"/>
<field name="price" string="Price"/>
<separator/>
<group expand="1" string="Group By">
<filter name="status" string="Status" context="{'group_by':'status'}"/>
</group>
</search>
</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="Property tag Form">
<sheet>
<group>
<field name="price" string="Price"/>
<field name="status" string="Status"/>
<field name="partner_id" string="Partner"/>
<field name="property_id" string="Property"/>
</group>
</sheet>
</form>
</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="Tags" editable="bottom">
<field name="price" string="Price"/>
<field name="status" string="Status"/>
<field name="validity" string="Validity"/>
<field name="date_deadline" string="Date deadline"/>
<field name="partner_id" string="Partner"/>
<field name="property_id" string="Property"/>
</list>
</field>
</record>

<record id="action_estate_property_offer" model="ir.actions.act_window">
<field name="name">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>
</odoo>
42 changes: 42 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tag_view_search" model="ir.ui.view">
<field name="name">estate.property.tag.search</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<search string="Property tags Search">
<field name="name" string="Tag Name"/>
</search>
</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 Form">
<sheet>
<group>
<field name="name" string="Name"/>
</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="Tags" editable="top">
<field name="name" string="Name"/>
</list>
</field>
</record>

<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Property tag Action</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
Loading