Skip to content
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
18 changes: 18 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
'name': 'Real Estate',
'category': 'Real Estate',
'version': '1.0',
'author': 'Radhey Detroja(RADET)',
'license': 'LGPL-3',
'summary': 'Manage real estate properties',
'depends': ['base'],
'application': True,
'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/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
110 changes: 110 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
from dateutil.relativedelta import relativedelta

from odoo import _, api, exceptions, fields, models
from odoo.tools.float_utils import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = "estate.property"
_description = "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.today() + relativedelta(months=3),
)
expected_price = fields.Float(required=True)
selling_price = fields.Float(
readonly=True,
copy=False,
)
best_price = fields.Float(compute="_compute_best_price", string="Best Offer")
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
garden_area = fields.Integer()
active = fields.Boolean(default=True)
garden_orientation = fields.Selection(
[('north', 'North'), ('south', 'South'), ('east', 'East'), ('west', 'West')]
)
total_area = fields.Integer(compute="_compute_total_area", string="Total Area (sqm)")
state = fields.Selection(
[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
],
required=True,
copy=False,
default='new',
)
property_type_id = fields.Many2one("estate.property.type")
buyer_id = fields.Many2one("res.partner", string="Buyer", copy=False)
salesperson_id = fields.Many2one("res.users", string="Salesperson", 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")

_expected_price_positive = models.Constraint(
'CHECK(expected_price > 0)',
'The expected price must be strictly positive.',
)

@api.depends("living_area", "garden_area")
def _compute_total_area(self):
for record in self:
record.total_area = (record.living_area or 0) + (record.garden_area or 0)

@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.0

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

def action_sold(self):
if self.filtered(lambda r: r.state == 'cancelled'):
raise exceptions.UserError(_("Cancelled properties cannot be sold."))
self.write({'state': 'sold'})
return True

def action_cancel(self):
if self.filtered(lambda x: x.state == 'sold'):
raise exceptions.UserError(_("Sold properties cannot be cancelled."))
self.write({'state': 'cancelled'})
return True

@api.constrains("selling_price", "expected_price", "buyer_id")
def _check_selling_price(self):
for record in self:
if float_is_zero(record.selling_price, precision_digits=2):
if record.buyer_id:
raise exceptions.ValidationError("The selling price must be greater than 0.")
continue
if float_compare(record.selling_price, 0.0, precision_digits=2) <= 0:
raise exceptions.ValidationError("The selling price must be positive.")
min_selling_price = record.expected_price * 0.9
if float_compare(record.selling_price, min_selling_price, precision_digits=2) < 0:
raise exceptions.ValidationError(
f"Minimum selling price: {min_selling_price:.2f}"
)

@api.ondelete(at_uninstall=False)
def _unlink_if_not_new_or_cancelled(self):
forbidden = self.filtered(lambda r: r.state not in ("new", "cancelled"))
if forbidden:
raise exceptions.UserError(_("Only properties in 'New' or 'Cancelled' state can be deleted."))
87 changes: 87 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from dateutil.relativedelta import relativedelta

from odoo import _, api, exceptions, fields, models


class EstatePropertyOffer(models.Model):
_name = "estate.property.offer"
_description = "Estate Property Offer Model"
_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("estate.property.type", related="property_id.property_type_id", store=True, string="Property Type")
validity = fields.Integer(default=7)
date_deadline = fields.Date(compute="_compute_date_deadline", inverse="_inverse_date_deadline", store=True)

_offer_price_positive = models.Constraint(
'CHECK(price > 0)',
'The offer price must be strictly positive.',
)

@api.model
def create(self, vals_list):
for vals in vals_list:
property_id = vals.get("property_id")
price = vals.get("price", 0.0)
if property_id:
property_record = self.env["estate.property"].browse(property_id)
if property_record:
existing_prices = property_record.offer_ids.mapped("price")
if existing_prices and price < max(existing_prices):
raise exceptions.UserError(_("You cannot create an offer with a lower amount than an existing offer."))
offers = super().create(vals_list)
properties = offers.mapped("property_id").filtered(lambda p: p.state == "new")
if properties:
properties.write({"state": "offer_received"})
return offers

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

def _inverse_date_deadline(self):
for record in self:
if record.date_deadline and record.create_date:
delta = record.date_deadline - record.create_date.date()
record.validity = delta.days
elif record.date_deadline:
delta = record.date_deadline - fields.Date.today()
record.validity = delta.days

def action_accept(self):
for record in self:
if record.property_id.buyer_id:
raise exceptions.UserError(_("An offer has already been accepted for this property."))
record.status = "accepted"
other_offers = record.property_id.offer_ids.filtered(lambda o: o.id != record.id)
other_offers.write({"status": "refused"})
record.property_id.selling_price = record.price
record.property_id.buyer_id = record.partner_id
record.property_id.state = "offer_accepted"
return True

def action_refuse(self):
for record in self:
if record.property_id.buyer_id == record.partner_id and record.property_id.state == "offer_accepted":
other_offers = record.property_id.offer_ids - record
has_other_offers = other_offers.filtered(lambda o: o.status != "refused")
record.property_id.write({
'selling_price': 0.0,
'buyer_id': False,
'state': 'offer_received' if has_other_offers else 'new'
})
record.status = "refused"
return True
15 changes: 15 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import fields, models


class PropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Property Tags"
_order = "name"

name = fields.Char(string="Name")
color = fields.Integer(string="Color")

_tag_name_unique = models.Constraint(
'UNIQUE(name)',
'The property tag name must be unique.',
)
23 changes: 23 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from odoo import api, fields, models


class PropertyType(models.Model):
_name = "estate.property.type"
_description = "Defines types of property"
_order = "sequence, name"

name = fields.Char(string="Name", required=True)
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(compute="_compute_offer_count", string="Number of Offers")
sequence = fields.Integer(string="Sequence", default=1)

_type_name_unique = models.Constraint(
'UNIQUE(name)',
'The property type name must be unique.',
)

@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 fields, models


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

property_ids = fields.One2many(
"estate.property",
"salesperson_id",
string="Active Properties",
domain=[('active', '=', True)],
)
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
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_menu_root" name="Real Estate">
<menuitem id="estate_properties_menu" name="Properties">
<menuitem id="estate_properties_menu_action" action="estate_property_action" />
</menuitem>
<menuitem id="estate_setting" name="Setting">
<menuitem id="estate_setting_type_action" action="estate_property_type_action" />
<menuitem id="estate_properties_menu_tag" action="estate_property_tag_action" />
</menuitem>
</menuitem>
</odoo>
39 changes: 39 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<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-danger="status == 'refused'" decoration-success="status == 'accepted'">
<field name="price" />
<field name="partner_id" />
<field name="validity" />
<field name="date_deadline" />
</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 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">Offers</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">list,form</field>
</record>
</odoo>
18 changes: 18 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="estate_property_tag_action" model="ir.actions.act_window">
<field name="name">Tag Name</field>
<field name="res_model">estate.property.tag</field>
<field name="view_mode">list,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" string="Type" />
</list>
</field>
</record>
</odoo>
Loading