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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,7 @@ dmypy.json

# Pyre type checker
.pyre/

# Ruff
ruff.toml
.ruff_cache/
26 changes: 26 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{ // Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "master",
"type": "debugpy",
"request": "launch",
"program": "/home/odoo/codebase/odoo/odoo-bin",
"args": [
"--addons-path=/home/odoo/codebase/odoo/addons,/home/odoo/codebase/enterprise, /home/odoo/codebase/tutorials",
"--limit-time-cpu=0",
"--limit-time-real=0",
"--limit-request=0",
"--database=rd-demo",
"--dev=xml,qweb",
"--with-demo",
//"-i", "accountant",
],
"console": "integratedTerminal",
"variablePresentation": {},
"justMyCode": false
},
]
}
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
26 changes: 26 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
'name': 'Real Estate',
'category': 'tutorials',
'summary': 'Real estate module to sell/rent houses and offices',
'description': """
immoweb alias xd
""",
'website': 'https://www.odoo.com/app/real_estate',
'depends': [
'base_setup',
'base',
],
'data': [
'security/ir.model.access.csv',
'views/estate_property_offer_view.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_views.xml',
'views/res_users_views.xml',
'views/estate_menus.xml',
],
'installable': True,
'application': True,
'author': 'ibrha',
'license': 'LGPL-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_user
124 changes: 124 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
from dateutil.relativedelta import relativedelta
from odoo import models, fields, api, exceptions
from odoo.tools.float_utils import float_compare


class Property(models.Model):
_name = "estate.property"
_description = "Estate Property app like immoweb"
_order = "id desc"

# computed fields
total_area = fields.Float(string="Total Area", compute="_compute_total_area")
best_price = fields.Float(compute="_compute_best_price")

# constraints
_positive_expected_price = models.Constraint(
'CHECK(expected_price > 0)',
'Expected price should be (strictly) positive',
)
_positive_selling_price = models.Constraint(
'CHECK(selling_price >= 0)',
'Selling price should be positive',
)

name = fields.Char(string="Title", required=True)
description = fields.Text(string="Description")
postcode = fields.Char(string="Postcode")
property_type_id = fields.Many2one("estate.property.type", string="Property Type")
offer_ids = fields.One2many("estate.property.offer", string="Offers", inverse_name="property_id")
tag_ids = fields.Many2many("estate.property.tag", string="Tags")
salesman = fields.Many2one("res.users", string="Salesman", default=lambda self: self.env.uid)
buyer = fields.Many2one("res.partner", string="Buyer", copy=False)
date_availability = fields.Date(string="Available From", default=fields.Date.today() + relativedelta(months=3), copy=False)
expected_price = fields.Float(string="Expected Price", required=True)
selling_price = fields.Float(string="Selling Price", readonly=True, copy=False)
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")
active = fields.Boolean(string="Active", default=True)
color = fields.Integer('Color Index', compute="_compute_color")
garden_orientation = fields.Selection(
string="Garden Orientation",
selection=[
("north", "North"),
("south", "South"),
("east", "East"),
("west", "West"),
],
)
state = fields.Selection(
string="Status",
selection=[
("new", "New"),
("offer_received", "Offer Received"),
("offer_accepted", "Offer Accepted"),
("sold", "Sold"),
("cancelled", "Cancelled"),
],
readonly=True,
default="new",
)

@api.depends("state")
def _compute_color(self):
for record in self:
if record.state == "sold":
record.color = 10 # Green
elif record.state == "cancelled":
record.color = 1 # Red
elif record.state == "offer_received":
record.color = 4 # Blue
elif record.state == "offer_accepted":
record.color = 3 # Light Green/Yellowish
else:
record.color = 0

@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")
def _compute_best_price(self):
for record in self:
record.best_price = max(record.offer_ids.mapped("price")) if record.offer_ids else 0

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

def sell_property(self):
for record in self:
if record.state == "cancelled":
raise exceptions.UserError("You cannot sell a cancelled property")
record.state = "sold"
return True

def cancel_property(self):
for record in self:
if record.state == "sold":
raise exceptions.UserError("You cannot cancel a sold property")
record.state = "cancelled"
return True

@api.constrains("state", "offer_ids")
def _check_selling_price(self):
for record in self:
for offer in record.offer_ids:
if offer.status == "accepted" and float_compare(offer.price, 0.9 * record.expected_price, 2) == -1:
raise exceptions.UserError("Selling price should be at least 90 percent of the expected price")

@api.ondelete(at_uninstall=False)
def unlink_property(self):
for property in self:
if property.state in ("sold", "cancelled"):
raise exceptions.UserError("You cannot delete a property that has existing offers")
69 changes: 69 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from odoo import models, fields, api, exceptions


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

# constraints
_positive_price = models.Constraint(
'CHECK(price > 0)',
'Offer price should be (strictly) positive',
)

price = fields.Float(string="Price", required=True)
validity = fields.Integer(string="Validity", default=7)
deadline_date = fields.Date(string="Deadline", compute="_compute_deadline_date", inverse="_inverse_deadline_date")
status = fields.Selection(
string="Status",
selection=[
("accepted", "Accepted"),
("refused", "Refused"),
],
copy=False,
)
property_id = fields.Many2one("estate.property", string="Property", required=True, ondelete="cascade")
partner_id = fields.Many2one("res.partner", string="Buyer", required=True)
property_type_id = fields.Many2one(related="property_id.property_type_id", store=True)

@api.depends("validity")
def _compute_deadline_date(self):
for record in self:
record.deadline_date = fields.Date.add(record.create_date or fields.Date.today(), days=record.validity)

def _inverse_deadline_date(self):
for record in self:
record.validity = (record.deadline_date - fields.Date.today()).days

def accept_offer(self):
for offer in self:
if offer.property_id.state == "sold":
raise exceptions.UserError(f"Property {offer.property_id.name} is already sold")
if offer.property_id.state == "offer_accepted":
raise exceptions.UserError(f"Property {offer.property_id.name} is already accepted")
if offer.property_id.state == "cancelled":
raise exceptions.UserError(f"Property {offer.property_id.name} is cancelled")
offer.property_id.buyer = offer.partner_id
offer.property_id.selling_price = offer.price
offer.property_id.state = "offer_accepted"
offer.status = "accepted"
return True

def refuse_offer(self):
for offer in self:
if offer.property_id.state == "sold":
raise exceptions.UserError(f"Property {offer.property_id.name} is already sold")
offer.status = "refused"
return True

@api.model
def create(self, vals):
for val in vals:
property_id = self.env['estate.property'].browse(val.get('property_id'))
if property_id.offer_ids:
max_price = max(property_id.offer_ids.mapped('price'))
if val.get('price') < max_price:
raise exceptions.UserError(f"Offer price should be at least {max_price}")
property_id.state = "offer_received"
return super().create(vals)
17 changes: 17 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import random
from odoo import models, fields


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

# constraints
_unique_tag = models.Constraint(
'UNIQUE(name)',
'Tag name should be unique',
)

name = fields.Char(string="Name", required=True)
color = fields.Integer('Color Index', default=lambda self: random.randint(1, 11))
24 changes: 24 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from odoo import models, fields, api


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

# constraints
_unique_type = models.Constraint(
'UNIQUE(name)',
'Type name should be unique',
)

name = fields.Char(string="Name", required=True)
property_ids = fields.One2many("estate.property", inverse_name="property_type_id", string="Properties")
sequence = fields.Integer(string="Sequence", default=1)
offer_ids = fields.One2many("estate.property.offer", inverse_name="property_type_id")
offer_count = fields.Integer(string="Offer Count", compute="_compute_offer_count")

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


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

property_ids = fields.One2many("estate.property", string="Assigned Properties", inverse_name="salesman")
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
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 @@
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_first_level_menu" name="Properties">
<menuitem id="estate_property_menu_action" name="Properties" action="estate_property_action"/>
</menuitem>
<menuitem id="settings_menu" name="Settings">
<menuitem id="estate_property_type_menu" name="Property Types" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu" name="Property Tags" action="estate_property_tag_action"/>
<menuitem id="estate_property_offer_menu" name="Property Offers" action="property_offer_view"/>
<menuitem id="users_and_companies" action="user_and_companies_view"/>
</menuitem>
</menuitem>
</odoo>
21 changes: 21 additions & 0 deletions estate/views/estate_property_offer_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<odoo>
<record id="property_offer_view" model="ir.actions.act_window">
<field name="name">Property Offer</field>
<field name="res_model">estate.property.offer</field>
<field name="view_mode">list,form</field>
</record>

<record id="property_offer_list_view" model="ir.ui.view">
<field name="name">property.offer.list.view</field>
<field name="model">estate.property.offer</field>
<field name="arch" type="xml">
<list string="Property Offers">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="deadline_date"/>
</list>
</field>
</record>
</odoo>
32 changes: 32 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?xml version="1.0"?>
<odoo>
<record id="estate_property_tag_view_list" model="ir.ui.view">
<field name="name">estate.property.tag.view.list</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<list string="Property Tags" editable="bottom">
<field name="name"/>
</list>
</field>
</record>

<record id="estate_property_tag_view_form" model="ir.ui.view">
<field name="name">estate.property.tag.view.form</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<form string="Property Tag">
<sheet>
<group>
<field name="name"/>
</group>
</sheet>
</form>
</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>
<field name="view_mode">list,form</field>
</record>
</odoo>
Loading