Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
16 changes: 16 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
'name': 'Real Estate',
'author': 'mila',
'license': 'LGPL-3',
'depends': ['base'],
'data': [
'security/ir.model.access.csv',
'views/estate_property_offer_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_views.xml',
'views/estate_menus.xml',
'views/res_users_views.xml',
],
'application': True,
}
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
117 changes: 117 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.tools.float_utils import float_compare, float_is_zero


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Real Estate Property"
_order = "id desc"

name = fields.Char(string="Title", required=True)
description = fields.Text(string="Description")
postcode = fields.Char(string="Postcode")
date_availability = fields.Date(string="Available From", copy=False, default=lambda self: date.today() + relativedelta(months=3))
expected_price = fields.Float(string="Expected Price", required=True)
_check_expected_price = models.Constraint(
"CHECK(expected_price > 0)", "Expected price should be positive."
)
selling_price = fields.Float(string="Selling Price", readonly=True, copy=False)
_check_selling_price = models.Constraint(
"CHECK(selling_price > 0)", "Selling price must be positive."
)
bedrooms = fields.Integer(string="Bedrooms", default=2)
living_area = fields.Integer(string="Living Area")
facades = fields.Integer(string="Facades")
garage = fields.Boolean(string="Garage")
garden = fields.Boolean(string="Garden")
garden_area = fields.Integer(string="Garden Area")

garden_orientation = fields.Selection(
selection=[
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West'),
],
string="Garden Orientation"
)

active = fields.Boolean(string="Active", default=True)

state = fields.Selection(
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled'),
],
string="Status",
required=True,
copy=False,
default='new'
)
property_type_id = fields.Many2one("estate.property.type", string="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")

total_area = fields.Integer(string="Total Area (sqm)", compute="_compute_total_area")
best_price = fields.Float(string="Best Offer", 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:
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 = False

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

def action_sold_property(self):
for property in self:
if property.state == "cancelled":
raise UserError("Cancelled properties cannot be sold.")
property.state = "sold"
return True

@api.constrains("expected_price", "selling_price")
def _check_selling_price(self):
for property in self:
if not float_is_zero(property.selling_price, precision_digits=2) and (
float_compare(
property.selling_price,
0.9 * property.expected_price,
precision_digits=2,
)
== -1
):
raise ValidationError("Selling price must be at least 90% of the expected price.")

@api.ondelete(at_uninstall=False)
def _check_offers_before_delete(self):
for property in self:
if property.state not in ['new', 'cancelled']:
raise UserError("Cannot delete a property that is currently active or sold.")
74 changes: 74 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from odoo import models, fields, api
from odoo.exceptions import UserError
from datetime import date, timedelta


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

price = fields.Float(string="Price")
_check_price = models.Constraint(
"CHECK(price > 0)", "Offer price must be positive."
)
status = fields.Selection(
selection=[
('accepted', 'Accepted'),
('refused', 'Refused'),
],
string="Status",
copy=False
)

partner_id = fields.Many2one("res.partner", string="Partner", required=True)
property_id = fields.Many2one("estate.property", string="Property", required=True)
property_type_id = fields.Many2one("estate.property.type", related="property_id.property_type_id", string="Property Type", store=True)
validity = fields.Integer(string="Validity (Days)", default=7)
date_deadline = fields.Date(string="Deadline", compute="_compute_date_deadline", inverse="_inverse_date_deadline")

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

def _inverse_date_deadline(self):
for record in self:
base_date = record.create_date.date() if record.create_date else date.today()
if record.date_deadline:
record.validity = (record.date_deadline - base_date).days
else:
record.validity = 7

def action_accept_offer(self):
for offer in self:
if any(status == "accepted" for status in offer.property_id.offer_ids.mapped("status")):
raise UserError("An offer has already been accepted for this property.")

offer.status = "accepted"
offer.property_id.buyer_id = offer.partner_id
offer.property_id.selling_price = offer.price
offer.property_id.state = "offer_accepted"
return True

def action_reject_offer(self):
for offer in self:
offer.status = "refused"
return True

@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
property_record = self.env['estate.property'].browse(vals.get('property_id'))

if property_record:
if property_record.offer_ids:
highest_existing_offer = max(property_record.offer_ids.mapped('price'))

if vals.get('price', 0) < highest_existing_offer:
raise UserError(("The offer price cannot be lower than an existing offer of %s.") % highest_existing_offer)

property_record.state = 'offer_received'

return super().create(vals_list)
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 = "Real Estate Property Tag"
_order = "name"

name = fields.Char(string="Name", required=True)
color = fields.Integer(string="Color")
_check_name_uniq = models.Constraint(
"unique (name)",
"Each 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 models, fields, api


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

active = fields.Boolean(default=True)
name = fields.Char(string="Name", required=True)
sequence = fields.Integer(string="Sequence", default=1, help="Used to order property types.")
_check_name_uniq = models.Constraint(
"unique (name)",
"Each property type name must be unique.",
)
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="Offers Count", compute="_compute_offer_count")

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


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

# FIXED
property_ids = fields.One2many(
"estate.property",
"salesperson_id",
string="Real Estate Properties",
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
22 changes: 22 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<odoo>
<data>
<!-- <menuitem id="estate_menu_root" name="Real Estate"/>

<menuitem id="estate_first_level_menu" name="Advertisements" parent="estate_menu_root"/>
<menuitem id="estate_model_menu_action" name="Properties" action="estate_property_action" parent="estate_first_level_menu"/>

<menuitem id="estate_menu_settings" name="Settings" parent="estate_menu_root"/>
<menuitem id="estate_menu_property_type" name="Property Types" action="estate_property_type_action" parent="estate_menu_settings"/>
<menuitem id="estate_menu_property_tag" name="Property Tags" action="estate_property_tag_action" parent="estate_menu_settings"/> -->

<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_menu_advertisements" name="Advertisements">
<menuitem id="estate_model_menu_action" action="estate_property_action"/>
</menuitem>
<menuitem id="estate_menu_settings" name="Settings">
<menuitem id="estate_menu_property_type" action="estate_property_type_action"/>
<menuitem id="estate_menu_property_tag" action="estate_property_tag_action"/>
</menuitem>
</menuitem>
</data>
</odoo>
44 changes: 44 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>

<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="Property Offers" editable="bottom"
decoration-success="status == 'accepted'"
decoration-danger="status == 'refused'">
<field name="price" string="Price"/>
<field name="partner_id" string="Partner"/>
<field name="validity" string="Validity (days)"/>
<field name="date_deadline" string="Deadline"/>

<button name="action_accept_offer" string="Accept" type="object" icon="fa-check" invisible="status or property_id.state in ['sold', 'cancelled']"/>
<button name="action_reject_offer" string="Refuse" type="object" icon="fa-times" invisible="status or property_id.state in ['sold', 'cancelled']"/>

<field name="status" string="Status"/>
</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">
<sheet>
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<field name="status"/>
</group>
</sheet>
</form>
</field>
</record>

</data>
</odoo>
21 changes: 21 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<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>
<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="Title"/>
<field name="color" widget="color_picker"/>
</list>
</field>
</record>
</data>
</odoo>
Loading