Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
24e63d0
[ADD] estate: create the app
Wajih-Wanis Oct 20, 2025
8d65340
[ADD] estate: added models and basic field
Wajih-Wanis Oct 20, 2025
a1f3314
[FIX] estate: fixed style
Wajih-Wanis Oct 20, 2025
2bdaaf8
[FIX] estate: fixed style
Wajih-Wanis Oct 20, 2025
8f77002
[ADD] estate: added security
Wajih-Wanis Oct 20, 2025
a032b7d
[FIX] estate: fixed typos
Wajih-Wanis Oct 21, 2025
f2f4add
[ADD] estate: added views
Wajih-Wanis Oct 21, 2025
5205845
[FIX] estate: styling fix
Wajih-Wanis Oct 21, 2025
ee692d4
[ADD] estate: added basic views
Wajih-Wanis Oct 21, 2025
8ed9368
[ADD] estate: added buyer, salesperson and estate type
Wajih-Wanis Oct 21, 2025
a150ae9
[FIX] estate: commit to save progress before debugging
Wajih-Wanis Oct 22, 2025
047a922
[ADD] estate: added offers for property
Wajih-Wanis Oct 22, 2025
9734e7f
[FIX] estate: runbot ci fixes
Wajih-Wanis Oct 22, 2025
1d50e6a
[ADD] estate: added computed fields and onchanges
Wajih-Wanis Oct 22, 2025
3b6035d
[ADD] estate: added action buttons
Wajih-Wanis Oct 22, 2025
2ff7967
[ADD] estate: added constraints
Wajih-Wanis Oct 22, 2025
98c5ac7
[IMP] estate: Add notes.
dery-odoo Oct 22, 2025
716e326
[ADD] estate: midway in adding sprinkles
Wajih-Wanis Oct 23, 2025
fef3a69
[ADD] estate: added all sprinkles
Wajih-Wanis Oct 23, 2025
5cb4327
[ADD] estate: added inheritance
Wajih-Wanis Oct 23, 2025
7f8e181
[ADD] estate: added inheritance
Wajih-Wanis Oct 24, 2025
73f89e1
[ADD] estate_account: added interact with other modules
Wajih-Wanis Oct 24, 2025
3375238
[ADD] estate: added kanban view
Wajih-Wanis Oct 24, 2025
b65cb6d
[IMP] estate: improve styles for estate module
Wajih-Wanis Oct 24, 2025
3a9b3fa
[IMP] estate_account: improve style for estate account
Wajih-Wanis Oct 24, 2025
8cd1bc6
[FIX] estate: fixing runbot test failures
Wajih-Wanis Oct 24, 2025
f548741
[IMP] estate: improve styles for estate module
Wajih-Wanis Oct 24, 2025
60a3801
[IMP] estate_account: improve style for estate account
Wajih-Wanis Oct 24, 2025
704e468
[FIX] estate: fixing runbot test failures
Wajih-Wanis Oct 24, 2025
e33fb32
[FIX] estate: fixing runbot test failures
Wajih-Wanis Oct 24, 2025
32f1597
[IMP] estate_account: improve style for estate account
Wajih-Wanis Oct 24, 2025
cdf8a12
[IMP] estate_account: improve style for estate account
Wajih-Wanis Oct 24, 2025
b9f9f94
[FIX] estate: fixing runbot test failures
Wajih-Wanis Oct 24, 2025
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
32 changes: 32 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
'name': "Real Estate",

'summary': """
Real Estate Tuto"
""",

'description': """
Starting tutorial real estate"
""",

'author': "Odoo",
'website': "https://www.odoo.com/",
'category': 'Tutorials',
'version': '0.1',
'application': True,
'depends': ['base'],

'data': [
'security/ir.model.access.csv',
'views/users_views.xml',
'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',
],
'assets': {

},
'license': 'AGPL-3'
}
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
98 changes: 98 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.tools.float_utils import float_is_zero
from dateutil.relativedelta import relativedelta


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

name = fields.Char('Title', required=True)
description = fields.Text('Description', required=True)

Choose a reason for hiding this comment

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

The string is generated from the variable name so no need to duplicate it here

notes = fields.Html()
postcode = fields.Char('Postcode', required=True)
date_availability = fields.Date('Available From', copy=False, default=fields.Datetime.now() + relativedelta(months=3))
expected_price = fields.Float('Expected Price', required=True)
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('Facades')
garage = fields.Boolean('Garage')
garden = fields.Boolean('Garden')
garden_area = fields.Integer('Garden area (sqm)')
garden_orientation = fields.Selection(
string='Garden Orientation',
selection=[('north', 'North'), ('south', 'South'), ('west', 'West'), ('east', 'East')],
)
active = fields.Boolean('Active', default=True)
state = fields.Selection(
string='State',
selection=[('new', 'New'), ('offer received', 'Offer Received'), ('offer accepted', 'Offer Accepted'), ('sold', 'Sold'), ('cancelled', 'Cancelled')],
help="Estate state",
default=('new')
)
property_type_id = fields.Many2one("estate.property.type", string="Type")
salesperson = fields.Many2one('res.users', string='Salesperson')
buyer = fields.Many2one('res.partner', string='Buyer')
tag_ids = fields.Many2many('estate.property.tag', string='Tag')
offer_ids = fields.One2many('estate.property.offer', 'property_id', string='Offer')
total_area = fields.Float(compute="_compute_total_area")
best_offer = fields.Float(compute="_compute_best_offer")
offer_accepted = fields.Boolean()

_expected_property_price_strictly_positive = models.Constraint(
'CHECK(expected_price > 0)',
)

_property_selling_price_positive = models.Constraint(
'CHECK(selling_price >= 0)',
)

@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") or [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 set_property_sold(self):
for record in self:
if record.state != 'cancelled':
record.state = 'sold'
else:
raise UserError("Can not cancel sold property")

def set_property_cancelled(self):
for record in self:
if record.state != 'sold':
record.state = 'cancelled'
else:
raise UserError("Can not cancel sold property")

@api.onchange('selling_price', 'expected_price')
@api.constrains('selling_price')
def _check_selling_price(self):
for record in self:
if not float_is_zero(record.selling_price, 2) and record.selling_price < 0.9 * record.expected_price:
raise UserError("Selling price can not be less than 90'%' of Excpected price")

@api.ondelete(at_uninstall=False)
def unlink_if_not_set(self):
for record in self:
if record.state not in {'new', 'cancelled'}:
raise UserError("Can not delete properties at this state")
return super().unlink()
59 changes: 59 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from odoo import fields, models, api
from odoo.exceptions import UserError
from dateutil.relativedelta import relativedelta
from datetime import datetime


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'Offer for the property'
_order = 'price desc'

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

_offer_price_strictly_positive = models.Constraint(
'CHECK(price > 0)',
)

@api.depends("validity")
def _compute_date_deadline(self):
for record in self:
record.date_deadline = fields.Datetime.now() + relativedelta(days=record.validity)

def _inverse_date_deadline(self):
for record in self:
deadline = datetime(record.date_deadline.year, record.date_deadline.month, record.date_deadline.day)
record.validity = int((deadline - fields.Datetime.now()).days)

def accept_offer(self):
for record in self:
if not record.property_id.offer_accepted:
record.status = 'accepted'
record.property_id.selling_price = record.price
record.property_id.buyer = record.partner_id
record.property_id.state = 'offer accepted'
record.property_id.offer_accepted = True
else:
raise UserError("Can not accept more than one offer")

def reject_offer(self):
for record in self:
record.status = 'refused'

@api.model
def create(self, vals_list):
if len(vals_list) > 0:
if len(self.env['estate.property'].browse(vals_list[0].get('property_id')).offer_ids.mapped('price')) and vals_list[0].get('price') < min(self.env['estate.property'].browse(vals_list[0].get('property_id')).offer_ids.mapped('price')):
raise UserError("Can not have an offer that is less the minimum offer")
self.env['estate.property'].browse(vals_list[0]['property_id']).state = 'offer received'
return super().create(vals_list)
16 changes: 16 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from odoo import fields, models


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Tags for the estate"
_order = 'name'

name = fields.Char('Tag', required=True)
description = fields.Char('Description')
color = fields.Integer()

_unique_tag = models.Constraint(
'UNIQUE(name)',
'Tag already exists'
)
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 fields, models, api


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

name = fields.Char('Type', required=True)
description = fields.Text()
property_ids = fields.One2many('estate.property', 'property_type_id')
sequence = fields.Integer('Sequence', default=1)
offer_ids = fields.One2many('estate.property.offer', 'property_type_id')
offer_count = fields.Integer(compute="_computer_offers_count")

_unique_type = models.Constraint(
'UNIQUE(name)',
'Property type name exists'
)

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


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

property_ids = fields.One2many('estate.property', 'salesperson', domain=[('state', 'in', ['new', 'offer received'])])
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_tag,estate.property.tag,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_property,estate.property,model_estate_property,base.group_user,1,1,1,1
access_estate_property_type,estate.property.type,model_estate_property_type,base.group_user,1,1,1,1
access_estate_property_offer,estate.property.offer,model_estate_property_offer,base.group_user,1,1,1,1
access_res_users,res.users,model_res_users,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" encoding="utf-8"?>
<odoo>
<menuitem id="estate_menu_root" name="Real Estate">
<menuitem id="estate_first_level_menu" name="Advertisements">
<menuitem id="estate_model_menu_action" action="estate_property_model_action"/>
</menuitem>
<menuitem id="estate_attributes_first_level_menu" name="Settings">
<menuitem id="estate_type_model_menu_action" action="estate_property_type_model_action"/>
<menuitem id="estate_tag_model_menu_action" action="estate_property_tag_model_action"/>
</menuitem>
</menuitem>
</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>

<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>
<h1>
<field name="price" string="Price"/>
<field name="partner_id" string="Partner id"/>
<field name="validity" string="Valid until"/>
<field name="date_deadline" string="Deadline"/>
</h1>
</group>
</sheet>
</form>
</field>
</record>

<record id="estate_property_offer_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 editable="top" decoration-success="status == 'accepted'" decoration-danger="status == 'refused'">
<field name="price"/>
<field name="partner_id"/>
<field name="validity"/>
<field name="date_deadline"/>
<button name="accept_offer" string="Accept" type="object" invisible="status in ('accepted', 'refused')" icon="fa-check"/>
<button name="reject_offer" string="Reject" type="object" invisible="status in ('accepted', 'refused')" icon="fa-times"/>
</list>
</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</field>
</record>

</odoo>
36 changes: 36 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>

<record id="estate_property_tag_model_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 editable="top">
<field name="name"/>
<field name="description"/>
</list>
</field>
</record>

<record id="estate_property_tag_view_form" model="ir.ui.view">
<field name="name">estate.property.tag.form</field>
<field name="model">estate.property.tag</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<h1>
<field name="name"/>
</h1>
</group>
</sheet>
</form>
</field>
</record>
</odoo>
Loading