Skip to content
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 property management module #110

Draft
wants to merge 14 commits into
base: 17.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 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
20 changes: 20 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
'name': 'Real Estate',
'version': '1.0',
'summary': 'Manage real estate properties',
'description': 'Module to manage real estate properties',
'author': 'Akya',
'depends': ['base', 'mail'],
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_offer_view.xml',
'views/estate_property_tag_view.xml',
'views/estate_property_type_view.xml',
'views/res_users_view.xml',
'views/estate_menus.xml',
],
'installable': True,
'application': True,
'license': 'AGPL-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_type, estate_property_tag, estate_property_offer, res_users

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from . import estate_property, estate_property_type, estate_property_tag, estate_property_offer, res_users
from . import estate_property
from . import estate_property_offer
from . import estate_property_tag
from . import estate_property_type
from . import res_users

Generally, we import every model in new lines.

107 changes: 107 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_is_zero, float_compare


class EstateProperty(models.Model):
_name = 'estate.property'
_description = 'Real Estate Property'
_order = 'id desc'
_inherit = ['mail.thread', 'mail.activity.mixin']

title = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
availability_date = fields.Date(copy=False)
expected_price = fields.Float(required=True, default=0.0)
selling_price = fields.Float(string="Selling Price", readonly=True)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
best_price = fields.Float(compute="_compute_best_price")
total_area = fields.Float(compute="_compute_total")
garden_orientation = fields.Selection([
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West'),
])
state = fields.Selection([
('new', 'New'),
('offer_received', 'Offer_received'),
('offer_accepted', 'Offer_accepted'),
('sold', 'Sold'),
('canceled', 'Canceled'),
('refused', 'Refused')
], string='Status', default='new', tracking=True)
active = fields.Boolean(string='Active', default=True)
property_type_id = fields.Many2one('estate.property.type', string="Property Type")
buyer_id = fields.Many2one('res.partner', string="Buyer")
seller_id = fields.Many2one('res.users', string="Salesperson", ondelete='set null')
tag_ids = fields.Many2many('estate.property.tag')
offer_ids = fields.One2many('estate.property.offer', 'property_id', string="Offers")

@api.depends('living_area', 'garden_area')
def _compute_total(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:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price'))
else:
record.best_price = 0.0

@api.onchange('garden')
def _onchange_garden(self):
if self.garden:
self.garden_area = 10.0
self.garden_orientation = 'north'
else:
self.garden_area = 0.0
self.garden_orientation = False

def action_cancel(self):
if self.state != "sold":
self.state = "canceled"
elif self.state == "sold":
raise UserError("This property can't be canceled as it is sold already")
return True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return True

I think it is not necessary check for all the actions.


def action_sold(self):
if self.state != "canceled":
if self.state == 'offer_accepted':
self.state = "sold"
else:
raise UserError("This property can't be sold as there are no offers")
elif self.state == "canceled":
raise UserError("This property can't be sold as it is canceled already")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif self.state == "canceled":
raise UserError("This property can't be sold as it is canceled already")
else:
raise UserError("This property can't be sold as it is canceled already")

Can't we use else directly instead of elif?

return True

_sql_constraints = [
('check_expected_price', 'CHECK(expected_price > 0)', 'The expected price must be strictly positive.'),
('check_selling_price', 'CHECK(selling_price >= 0)', 'The selling price must be positive.')
]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally we define constraints just after field defination.


@api.constrains('selling_price', 'expected_price')
def _check_selling_price(self):
for record in self:
if not float_is_zero(record.selling_price, precision_rounding=0.01):
if float_compare(record.selling_price, record.expected_price * 0.9, precision_rounding=0.01) == -1:
raise ValidationError("The selling price cannot be lower than 90% of the expected price.")

def action_offer_received(self):
self.state = 'offer_received'

@api.ondelete(at_uninstall=False)
def _delete_property(self):
for record in self:
if record.state not in ['new', 'canceled']:
raise UserError(
"You cannot delete a property unless it is in the 'New' or 'Canceled' state."
)
63 changes: 63 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from odoo import models, fields, api
from datetime import timedelta

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
from odoo import models, fields, api
from datetime import timedelta
from datetime import timedelta
from odoo import api, fields, model

Generally we follow a convention in which we first we import all the external libraries and then the import of odoos in alphabetical order.

from odoo.exceptions import UserError


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Property Offer'
_order = 'price desc'

price = fields.Float(string="Price")
status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], string="Status", copy=False)
partner_id = fields.Many2one('res.partner', string="Partner", required=True)
validity = fields.Integer(string="Validity (days)", default=7)
date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline")
property_id = fields.Many2one('estate.property', 'Property', required=True, ondelete="cascade")
property_type_id = fields.Many2one(related="property_id.property_type_id")

@api.depends('create_date', 'validity')
def _compute_date_deadline(self):
for record in self:
if record.create_date:
record.date_deadline = record.create_date + timedelta(days=record.validity)
else:
record.date_deadline = fields.Date.today() + timedelta(days=record.validity)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
for record in self:
if record.create_date:
record.date_deadline = record.create_date + timedelta(days=record.validity)
else:
record.date_deadline = fields.Date.today() + timedelta(days=record.validity)
record.deadline_date = record.property_id.create_date + relativedelta(
days=record.validity
)


def _inverse_date_deadline(self):
for record in self:
if record.date_deadline and record.create_date:
create_date_date = record.create_date.date()
record.validity = (record.date_deadline - create_date_date).days
else:
record.validity = 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if record.date_deadline and record.create_date:
create_date_date = record.create_date.date()
record.validity = (record.date_deadline - create_date_date).days
else:
record.validity = 0
record.validity = (record.deadline_date - record.property_id.create_date.date()).days

Can you give it a try? Make sure to check all possible cases.
same for compute also


def action_accept(self):
if not self.property_id.buyer_id:
self.status = 'accepted'
self.property_id.selling_price = self.price
self.property_id.buyer_id = self.partner_id
self.property_id.state = "offer_accepted"
else:
raise UserError("Offer has been already Accepted")
return True

def action_refuse(self):
if self.property_id.buyer_id == self.partner_id:
self.property_id.buyer_id = ''
self.property_id.selling_price = 0
self.status = 'refused'
return True

_sql_constraints = [

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here for constraints

('check_offer_price', 'CHECK(price > 0)', 'The offer price must be strictly positive.')
]

@api.model
def create(self, vals):
record = super().create(vals)
if record.property_id.state == 'new':
record.property_id.state = 'offer_received'
if record.price < max(record.property_id.offer_ids.mapped('price')):
raise UserError("Price should be greater than the existing offer")
return record
14 changes: 14 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from odoo import models, fields


class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'Property Tag'
_order = 'name'

name = fields.Char(string="Name", required=True)
color = fields.Integer()

_sql_constraints = [
('unique_name', 'UNIQUE(name)', 'This property tag already exists.'),
]
25 changes: 25 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from odoo import models, fields, api


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

name = fields.Char(string="Name", required=True)
sequence = fields.Integer(string='Sequence', default=10)
property_ids = fields.One2many('estate.property', 'property_type_id', string="Properties")
offer_ids = fields.One2many('estate.property.offer', 'property_type_id', string="Offers")
offer_count = fields.Integer(
string="Number of Offers",
compute='_compute_offer_count'
)

_sql_constraints = [
('unique_name', 'UNIQUE(name)', 'This property type already exists.'),
]

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


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

property_ids = fields.One2many(
'estate.property',
'seller_id',
string='Properties',
domain=[('state', '=', 'available')]
)
7 changes: 7 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
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_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


13 changes: 13 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="Advertisements" name="Advertisements">
<menuitem id="Properties" action="action_estate_property"/>
</menuitem>
<menuitem id="Settings" name="Settings">
<menuitem id="Properties_Types" name="Property Types" action="action_estate_property_type"/>
<menuitem id="Properties_Tags" name="Property Tags" action="action_estate_property_tag"/>
<menuitem id="Users_and_companies" name="Users and Companies" action="action_res_users"/>
</menuitem>
</menuitem>
</odoo>
51 changes: 51 additions & 0 deletions estate/views/estate_property_offer_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?xml version="1.0"?>
<odoo>


<record id="action_estate_property_offer" model="ir.actions.act_window">
<field name="name">Property Offers</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>
</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>
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity" />
<field name="date_deadline" />

</group>
</sheet>
</form>
</field>
</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 string="Property Offers" editable="bottom" decoration-success="status in ['accepted']" decoration-danger="status in ['refused']">
<field name="partner_id"/>
<field name="price"/>
<field name="validity"/>
<field name="status" column_invisible='true'/>
<field name="date_deadline"/>
<field name="property_type_id"/>
<button name="action_accept" type="object" icon="fa-check" invisible="status in ['accepted', 'refused']"/>
<button name="action_refuse" type="object" icon="fa-times" invisible="status in ['accepted', 'refused']"/>
</tree>
</field>
</record>


</odoo>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a blank line at EOF

38 changes: 38 additions & 0 deletions estate/views/estate_property_tag_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>

<record id="action_estate_property_tag" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">tree,form</field>
</record>

<record id="view_estate_property_tag_tree" model="ir.ui.view">
<field name="name">estate.property.tag.tree</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<tree string="Property Tags" editable="bottom">
<field name="name"/>
<field name="color"/>
</tree>
</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="Estate Property Tag">
<sheet>
<group>
<h1> <field name="name"/> </h1>
</group>
</sheet>
</form>
</field>
</record>

</odoo>



Loading