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 module #581

Draft
wants to merge 2 commits into
base: 17.0
Choose a base branch
from
Draft
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
14 changes: 14 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
'name': 'Real Estate Management',
'depends': ['base'],
'application': True,
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/res_users_views.xml',
'views/estate_menus.xml',
]
}
5 changes: 5 additions & 0 deletions estate/models/__init__.py
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
121 changes: 121 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from odoo import fields, models, api
from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_utils

class EstateProperty(models.Model):

_name = 'estate.property'
_description = 'Real Estate Property Model'
_order = "id desc"

name = fields.Char(required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False, default=lambda self: fields.Date.add(fields.Date.today(), months=3))
expected_price = fields.Float(required=True,)
selling_price = fields.Float(readonly=True, copy=False)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
[('north','North'),('south', 'South'), ('east', 'East'), ('west','West')])
active = fields.Boolean(default=True)
state = fields.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')
tag_ids = fields.Many2many('estate.property.tag')
user_id = fields.Many2one('res.users', string='Salesperson', default=lambda self: self.env.user)
partner_id = fields.Many2one('res.partner', string='Buyer', copy=False)
offer_ids = fields.One2many('estate.property.offer','property_id')
total_area = fields.Float(compute='_compute_total_area')
best_price = fields.Float(compute='_compute_best_price')


@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:
record.best_price = max(record.offer_ids.mapped('price') or [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_button_cancel(self):
for record in self:
if self.state == 'sold':
raise UserError ('A sold property cannot be canceled')
else:
record.state = 'canceled'
return True


def action_button_sold(self):
for record in self:
if self.state == 'canceled':
raise UserError ('A canceled property cannot be sold')
else:
record.state = 'sold'
return True

@api.constrains('expected_price')
def _check_expected_price(self):
for record in self:
if record.expected_price < 0:
raise ValidationError('The Expected Price should be positive.')


@api.constrains('selling_price')
def _check_selling_price(self):
for record in self:
if record.selling_price < 0:
raise ValidationError('The Selling Price should be positive.')


@api.constrains('selling_price', 'expected_price', 'offer_ids.price', 'offer_ids.status')
def _check_offer_selling_price(self):
for record in self:
if 'accepted' in record.offer_ids.mapped('status') and record.selling_price >= 0 and record.expected_price > 0:
min_selling_price = 0.9*record.expected_price
if float_utils.float_compare(record.selling_price, min_selling_price, precision_digits=2) == -1:
raise ValidationError(
'Selling Price should be higher than 90 percent of the Expected Price. Reduce the expected price if you want accept this offer')


@api.ondelete(at_uninstall=False)
def _unlink_if_property_new_canceled(self):
for record in self:
if record.state in ('offer_received','offer_accepted','sold'):
raise UserError("Can't delete an active property!")


#2024-02-02 20:53:56,630 442 WARNING real_estate odoo.models: method estate.property._check_offer_selling_price: @constrains parameter 'offer_ids.price' is not a field name
#2024-02-02 20:53:56,630 442 WARNING real_estate odoo.models: method estate.property._check_offer_selling_price: @constrains parameter 'offer_ids.status' is not a field name
# to_do: Make the estate.property.tag list views editable.
# state in property list view make it invisible?
# status in property offer list view make it invisible?
# chapter 12: stat button on estate.property.type pointing to the estate.property.offer action??
# use the type="action" attribute
# At this point, clicking on the stat button should display all offers.
# We still need to filter out the offers. On the estate.property.offer action, add a domain that defines property_type_id as equal to the active_id (= the current record
# chapter 13: Add the property_ids field to the base.view_users_form in a new notebook page.
75 changes: 75 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from odoo import fields, models, api
from odoo.exceptions import UserError

class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Real Estate Property Offers'
_order = "price desc"


price = fields.Float()
status = fields.Selection([('accepted', 'Accepted'), ('refused', 'Refused')], copy=False)
partner_id = fields.Many2one('res.partner', required=True)
property_id = fields.Many2one('estate.property', required=True)
property_type_id = fields.Many2one(related="property_id.property_type_id", store=True)
validity = fields.Integer(default=7)
date_deadline = fields.Date(compute='_compute_date_deadline', inverse= '_inverse_date_deadline')


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


def _inverse_date_deadline(self):
for record in self.filtered('date_deadline'):
create_date = (record.create_date or fields.Datetime.now()).date()
record.validity = (record.date_deadline - create_date).days


@api.model
def create(self, vals):

res = super(EstatePropertyOffer, self).create(vals)

existing_offer = self.search([
('property_id', '=', vals.get('property_id')),
('price', '>', vals.get('price')),
], limit=1)

if existing_offer:
raise UserError("Cannot create an offer with a lower price than an existing one.")

self.env['estate.property'].browse(vals.get('property_id')).write({'state':'offer_received'})

return res


def action_button_refuse(self):
for record in self:
if self.status == 'accepted':
raise UserError ('Cannot refuse an accepted offer')
else:
record.status = 'refused'
return True


def action_button_accept(self):
for record in self:
if 'accepted' in record.property_id.offer_ids.mapped('status'):
raise UserError ('Cannot accept more than one offer')
else:
record.status = 'accepted'
record.property_id.state = 'offer_accepted'
record.property_id.selling_price = record.price
record.property_id.partner_id = record.partner_id
return True


@api.constrains('price')
def _check_offer_price(self):
for record in self:
if record.price < 0:
raise UserError('The Offer Price should be positive.')

22 changes: 22 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from odoo import fields, models, api
from odoo.exceptions import ValidationError


class EstatePropertyTag(models.Model):
_name = 'estate.property.tag'
_description = 'Real Estate Property Tags'
_order = "name desc"
_sql_constraints = [('tag_unique', 'unique (name)','Property tag name should be unique!')]

name = fields.Char(required=True)
color = fields.Integer()

# @api.constrains('name')
# def _check_name(self):
# for record in self:
# if record.name in record.name.mapped():
# raise ValidationError('Property tag name should be unique!')




34 changes: 34 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from odoo import fields, models, api


class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = 'Real Estate Property Types'
_order = "sequence, name desc"
_sql_constraints = [
('type_unique', 'unique (name)','Property type name should be unique!')]

name = fields.Char(required=True)
sequence = fields.Integer('Sequence', default=1, help="Used to order types")
property_ids = fields.One2many('estate.property', 'property_type_id')
offer_ids = fields.One2many('estate.property.offer', 'property_type_id')
offer_count = fields.Integer(compute='_compute_offer_count')

@api.depends('offer_ids')
def _compute_offer_count(self):
for record in self:
record.offer_count =len(record.property_ids.mapped('offer_ids'))


class EstatePropertyTypeLine(models.Model):
_name = 'estate.property.type.line'
_description = 'Real Estate Property Types Line'


property_ids = fields.Many2one('estate.property.type')
name = fields.Char()
expected_price = fields.Float()
state = fields.Selection([('new', 'New'), ('offer_received', 'Offer Received'),('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),('canceled', 'Canceled')],required=True, copy=False, default='new')


10 changes: 10 additions & 0 deletions estate/models/res_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from odoo import fields, models


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


property_ids = fields.One2many('estate.property','user_id',domain="[('user_id', '=', id)]")


6 changes: 6 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
estate.access_estate_property,access_estate_property,estate.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
estate.access_estate_property_type_line,access_estate_property_type_line,estate.model_estate_property_type_line,base.group_user,1,1,1,1
9 changes: 9 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<menuitem id="real_estate_menu" name="Real Estate"/>
<menuitem id="advertisements_menu" name="Advertisements" parent="real_estate_menu"/>
<menuitem id="properties_menu" action="estate_property_action" parent="advertisements_menu"/>
<menuitem id="settings_menu" name="Settings" sequence="100" parent="real_estate_menu"/>
<menuitem id="property_types_menu" action="estate_property_type_action" parent="settings_menu"/>
<!-- <menuitem id="property_tag_menu" action="estate_property_tag_action" parent="settings_menu"/> -->
</odoo>
43 changes: 43 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="estate_property_offer_view_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="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="action_button_accept" type="object" icon="fa-check" title="Accept Offer" invisible="status in ('accepted', 'refused')"/>
<button name="action_button_refuse" type="object" icon="fa-times" title="Refuse Offer" invisible="status in ('accepted', 'refused')"/>
<field name="status" invisible="1"/>
</tree>
</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>
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>

<!-- <record id="estate_property_offer_action" model="ir.actions.act_window">
<field name="name">Estate Property Offers</field>
<field name="res_model">estate.property.offer</field>
<field name="domain">[('property_type_id', '=', active_id)]</field>
<field name="context">{'default_property_type_id': active_id}</field>
</record> -->
</odoo>
19 changes: 19 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<record id="estate_property_tag_view_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"/>
</tree>
</field>
</record>

<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
</record>
</odoo>


Loading