Skip to content
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
17 changes: 17 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
'name': "Real Estate",
'summary': "Testing the real estate module",
'description': "Testing the description real estate module",
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/estate_property_offer_view.xml',
'views/estate_property_view.xml',
'views/estate_property_type_view.xml',
'views/estate_property_tag_view.xml',
'views/estate_menus.xml',
],
'application': True,
'author': "Odoo",
'license': 'LGPL-3',
}
6 changes: 6 additions & 0 deletions estate/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from . import (
estate_property,
estate_property_offer,
estate_property_tag,
estate_property_type,
)
112 changes: 112 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Estate property model"

_check_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'Expected price of the property must be strictly positive.',
)

_check_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'Selling price of the property must be strictly positive.',
)

name = fields.Char("Title", required=True, default="Unknown")
description = fields.Text("Description")
postcode = fields.Char("Postcode")
expected_price = fields.Float("Expected Price", required=True)
date_availability = fields.Date(
"Available From",
default=fields.Date.add(fields.Date.today(), months=3),
copy=False,
)
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()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string="Orientation",
selection=[("north", "N"), ("south", "S"), ("east", "E"), ("west", "W")],
help="Orientation of the garden",
)
last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now)
active = fields.Boolean(default=True)
state = fields.Selection(
string="State",
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),

Choose a reason for hiding this comment

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

We usually use US english as default language. You can keep it here for the tutorial, just saying so you know

],
default="new",
)

property_type_id = fields.Many2one(
'estate.property.type', string='Property Type', ondelete='restrict'
)

salesperson_id = fields.Many2one(
'res.users',
string='Salesman',
ondelete='restrict',
default=lambda self: self.env.user.partner_id,
)

buyer_id = fields.Many2one(
'res.partner', string='Buyer', ondelete='restrict', copy=False, readonly=True,
)

property_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")
best_offer = fields.Float(compute="_compute_best_offer")

@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")
def _compute_best_offer(self):
for record in self:
record.best_offer = max(record.offer_ids.mapped('price'), default=0.0)

@api.onchange("garden")
def _onchange_garden(self):
if self.garden:
self.garden_area, self.garden_orientation = 10, "north"
else:
self.garden_area, self.garden_orientation = 0, None

def action_mark_as_sold(self):
for record in self:
if record.state == "cancelled":
raise UserError(_("Canceled properties cannot be sold."))
record.state = "sold"
return True

def action_mark_as_cancelled(self):
for record in self:
if record.state == "sold":
raise UserError(_("Sold properties cannot be cancelled."))
record.state = "cancelled"
return True

@api.constrains('expected_price')
def _check_expected_price(self):
for record in self:
for offer in record.offer_ids:
if offer.status == "yes" and float_compare(offer.price, 0.9 * record.expected_price, 2) < 0:
raise ValidationError("There is an accepted offer for an amount less than 90 percent of the new selling price, action required!")
76 changes: 76 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate property offer"

_check_offer_price = models.Constraint(
'CHECK(price > 0)', 'Offer price must be strictly positive.'
)

@api.model_create_multi
def create(self, vals_list):
records = super().create(vals_list)
for record in records:
if record.property_id.state == "new":
record.property_id.state = "offer_received"
return records

price = fields.Float("Expected Price")
status = fields.Selection(
string="Status", selection=[("yes", "Accepted"), ("no", "Refused")], copy=False
)
validity = fields.Integer(default=7)
offer_creation_date = fields.Date(default=fields.Date.today())
deadline = fields.Date(
"Offer Deadline", compute="_compute_deadline", inverse="_inverse_deadline",
)

partner_id = fields.Many2one(
'res.partner',
string='Buyer',
ondelete='restrict',
default=lambda self: self.env.user.partner_id,
required=True,
)
property_id = fields.Many2one(
'estate.property', string='Property', ondelete='restrict', required=True
)

@api.depends("validity", "offer_creation_date")
def _compute_deadline(self):
for record in self:
record.deadline = fields.Date.add(
record.offer_creation_date, days=record.validity
)

def _inverse_deadline(self):
for record in self:
record.validity = (record.deadline - record.offer_creation_date).days

def action_accept_offer(self):
for record in self:
for offer in record.property_id.offer_ids:
if offer.status == "yes":
raise UserError(_("Already accepted an offer for this property."))
if float_compare(record.price, 0.9 * record.property_id.expected_price, 2) < 0:
raise ValidationError("Accepted offer price cannot be less than 90 percent of property price")
record.status = "yes"
record.property_id.buyer_id = record.partner_id
record.property_id.selling_price = record.price
record.property_id.state = "offer_accepted"
return True

def action_reject_offer(self):
self.status = 'no'
self.property_id.write({'buyer_id': None, 'selling_price': 0})
return True

@api.constrains('price')
def _check_offered_price(self):
for record in self:
if float_compare(record.price, 0.9 * record.property_id.expected_price, 2) < 0:
raise ValidationError("Offer price cannot be less than 90 percent of property price")
12 changes: 12 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Estate property tag"
_name_unique = models.Constraint(
'unique (name)',
'A property tag of that name already exists',
)

name = fields.Char('Property Tag', required=True)
12 changes: 12 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import fields, models


class EstatePropertyType(models.Model):
_name = "estate.property.type"
_description = "Estate property type"
_name_unique = models.Constraint(
'unique (name)',
'A property type of that name already exists',
)

name = fields.Char('Property Type', required=True)
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
access_estate_property,access_estate_property,model_estate_property,base.group_system,1,1,1,1
access_estate_property_type,access_estate_property_type,model_estate_property_type,base.group_system,1,1,1,1
access_estate_property_tag,access_estate_property_tag,model_estate_property_tag,base.group_system,1,1,1,1
access_estate_property_offer,access_estate_property_offer,model_estate_property_offer,base.group_system,1,1,1,1
view_estate_property,view_estate_property,model_estate_property,base.group_user,1,0,0,0
12 changes: 12 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0"?>
<odoo>
<menuitem id="estate_main_menu" name="Real Estate">
<menuitem id="advertisement_menu" name="Advertisment">
<menuitem id="property_menu_action" action="estate_property_action"/>
</menuitem>
<menuitem id="settings_menu" name="Settings">
<menuitem id="property_types_menu_action" action="estate_property_types_action"/>
<menuitem id="property_tags_menu_action" action="estate_property_tags_action"/>
</menuitem>
</menuitem>
</odoo>
40 changes: 40 additions & 0 deletions estate/views/estate_property_offer_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0"?>
<odoo>

<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="Estate Property Offer Form">
<sheet>
<div>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="deadline"/>
<field name="status"/>
</group>
</div>
</sheet>
</form>
</field>
</record>

<record id="estate_property_offer_view_tree" 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="Channel">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="deadline"/>
<field name="status"/>
<button name="action_accept_offer" string="Accept" type="object" icon="fa-check"/>
<button name="action_reject_offer" string="Reject" type="object" icon="fa-remove"/>
</list>
</field>
</record>

</odoo>
8 changes: 8 additions & 0 deletions estate/views/estate_property_tag_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tags_action" model="ir.actions.act_window">
<field name="name">Property Tags</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list</field>
</record>
</odoo>
8 changes: 8 additions & 0 deletions estate/views/estate_property_type_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_types_action" model="ir.actions.act_window">
<field name="name">Property Types</field>
<field name="res_model">estate.property.type</field>
<field name="view_mode">list</field>
</record>
</odoo>
Loading