Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2604af7
[ADD] added estate module
VIWAR-ODOO Feb 5, 2026
d82ef23
[FIX] delete test data
VIWAR-ODOO Feb 5, 2026
51763b2
[FIX] added application true and installable as true
VIWAR-ODOO Feb 5, 2026
dbcbc7a
[FIX] formate change of manifest file
VIWAR-ODOO Feb 5, 2026
1b9af85
[FIX] removed duplicate author fiels inthe manifest file
VIWAR-ODOO Feb 5, 2026
265e0f4
[FIX] style error removed white spaces
VIWAR-ODOO Feb 5, 2026
0aaabf0
[FIX] no newline error in style fixed removed extra comma
VIWAR-ODOO Feb 5, 2026
5a196fc
[FIX] formated code
VIWAR-ODOO Feb 5, 2026
e97eeb1
[FIX] style error
VIWAR-ODOO Feb 5, 2026
2e78623
[FIX] new line at end , fix error no new newline at the end
VIWAR-ODOO Feb 5, 2026
21041c2
[CLN] remove description
VIWAR-ODOO Feb 5, 2026
828ecf1
[FIX] restyle for test case eror
VIWAR-ODOO Feb 5, 2026
c2e6a0c
[ADD] added estate_property.py model
VIWAR-ODOO Feb 6, 2026
dc89be2
[IMP] estate: Update estate/models/estate_property.py
VIWAR-ODOO Feb 19, 2026
0134897
[IMP] estate : added extra line
VIWAR-ODOO Feb 19, 2026
7955c6a
[IMP] estate: Update estate/models/estate_property.py
VIWAR-ODOO Feb 20, 2026
230f419
[IMP] estate: removed <data> tag , and added "
VIWAR-ODOO Feb 20, 2026
02685cc
[ADD] estate: added the form view and a notebook and added two tabs i…
VIWAR-ODOO Feb 20, 2026
bd0fb4d
[ADD] estate: search added, filter and domain added
VIWAR-ODOO Feb 23, 2026
e055ab4
[ADD] estate: added the property type , implemented the many2one
VIWAR-ODOO Feb 23, 2026
f943aba
[LINT] estate: fix formatting to pass R&D tests
VIWAR-ODOO Feb 24, 2026
7096674
[LINT] estate: fix styling , white space
VIWAR-ODOO Feb 24, 2026
465a892
[IMP] estate: added the the lambda function to acess the self.env._uid
VIWAR-ODOO Feb 24, 2026
1f9a81a
[ADD] estate: added many2many , tag model and implementation
VIWAR-ODOO Feb 24, 2026
23f615e
[ADD] estate: added offers and implemented one2many
VIWAR-ODOO Feb 24, 2026
16c7370
[ADD] estate: added the depends compute field
VIWAR-ODOO Feb 25, 2026
c6c5ce1
[ADD] estate: added the property offer
VIWAR-ODOO Feb 26, 2026
22d1eeb
[ADD] estate: maintainance task done during the meeting
VIWAR-ODOO Feb 26, 2026
1784e4c
[ADD] estate: adde the onchange on the garden to set the garden orien…
VIWAR-ODOO Feb 27, 2026
54239dd
[FIX] estate: fixed the style error
VIWAR-ODOO Feb 27, 2026
7814cfc
[ADD] estate: added the button for the custom logic on the properties…
VIWAR-ODOO Feb 27, 2026
a5eb998
[ADD] estate: added the offer functionality
VIWAR-ODOO Mar 2, 2026
93381c4
[FIX] estate: completed ch -8-9, fixed the actions on specific condit…
VIWAR-ODOO Mar 5, 2026
3eec925
[FIX] estate: fixed style error
VIWAR-ODOO Mar 6, 2026
78f768b
[ADD] estate: added some functionality as per the meeting - button di…
VIWAR-ODOO Mar 6, 2026
1cf6ae6
[FIX] estate: fixed the styling error
VIWAR-ODOO Mar 6, 2026
fe493f2
[ADD] estate: done ch-10 and also completed the task of adding the de…
VIWAR-ODOO Mar 9, 2026
37cabae
[ADD] estate: meeting task
VIWAR-ODOO Mar 11, 2026
f378879
[ADD] estate: added ch-11
VIWAR-ODOO Mar 12, 2026
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
25 changes: 25 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
'name': 'Real Estate',
'version': '1.0',
'depends': ['base'],
'author': 'viwar-odoo',
'category': 'real estate',
'description': "real estate App.",
"data": [
"security/ir.model.access.csv",
"views/estate_maintainance_form.xml",
"views/estate_property_visit_calender.xml",
"views/estate_property_visit_kanban_view.xml",
"views/estate_property_offer_view.xml",
"views/estate_property_tag_view.xml",
"views/estate_property_type_view.xml",
"views/estate_property_visit_action.xml",
"views/estate_property_view.xml",
"views/estate_menus.xml",
#"data/estate_property_demo.xml"
],
'application': True,
'installable': True,
'license': 'LGPL-3',
'website': 'https://odoo.com',
}
31 changes: 31 additions & 0 deletions estate/data/estate_property_demo.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0"?>
<odoo>
<data >


<record id="estate_property_tag_1" model="estate.property.tag">
<field name="id">1</field>
<field name="name">tag1</field>
</record>

<record id="estate_property_type_1" model="estate.property.type">
<field name="id">1</field>
<field name="name">type1</field>
</record>

<record id="estate_property_1" model="estate.property">
<field name="id">1</field>
<field name="name">property</field>
<field name="tag_ids" eval="[Command.set([ref('estate_property_tag_1')])]" />
<field name="property_type_id">1</field>
</record>

<record id="estate_property_offer1" model="estate.property.offer">
<field name="id">1</field>
<field name="price">100000</field>
<field name="property_id">1</field>
</record>


</data>
</odoo>
Comment on lines +2 to +31

Choose a reason for hiding this comment

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

Suggested change
<odoo>
<data >
<record id="estate_property_tag_1" model="estate.property.tag">
<field name="id">1</field>
<field name="name">tag1</field>
</record>
<record id="estate_property_type_1" model="estate.property.type">
<field name="id">1</field>
<field name="name">type1</field>
</record>
<record id="estate_property_1" model="estate.property">
<field name="id">1</field>
<field name="name">property</field>
<field name="tag_ids" eval="[Command.set([ref('estate_property_tag_1')])]" />
<field name="property_type_id">1</field>
</record>
<record id="estate_property_offer1" model="estate.property.offer">
<field name="id">1</field>
<field name="price">100000</field>
<field name="property_id">1</field>
</record>
</data>
</odoo>
<odoo>
<record id="estate_property_tag_1" model="estate.property.tag">
<field name="id">1</field>
<field name="name">tag1</field>
</record>
<record id="estate_property_type_1" model="estate.property.type">
<field name="id">1</field>
<field name="name">type1</field>
</record>
<record id="estate_property_1" model="estate.property">
<field name="id">1</field>
<field name="name">property</field>
<field name="tag_ids" eval="[Command.set([ref('estate_property_tag_1')])]" />
<field name="property_type_id">1</field>
</record>
<record id="estate_property_offer1" model="estate.property.offer">
<field name="id">1</field>
<field name="price">100000</field>
<field name="property_id">1</field>
</record>
</odoo>

I don't think there would be any use of this data tag unless you want to use it with noupdate

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_maintainance_request
from . import estate_property
from . import estate_property_offer
from . import estate_property_tag
from . import estate_property_type
from . import estate_property_visit
37 changes: 37 additions & 0 deletions estate/models/estate_maintainance_request.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from odoo import api, models, fields


class EstateMaintainanceRequest(models.Model):
_name = 'estate.maintainance.request'
_description = 'table for the technician maintainance request'

property_id = fields.Many2one('estate.property', required=True, readonly=True)
buyer_id = fields.Many2one('res.users', required=True, default='self.env.uid')
technician_id = fields.Many2one('res.partner', required=False)
state = fields.Selection(
string='state',
default='new',
selection=[
('new', "New"),
('assigned', "Assigned"),
('inprogress', "Inprogress"),
('done', "Done"),
('cancelled', "Cancelled"),
],
)
estimate_cost = fields.Float(required=False)
actual_cost = fields.Float(compute='_compute_actual_cost', store=True)
current_stage = fields.Char(compute='_compute_state_after_assigned', store=True)

@api.depends('state', 'estimate_cost')
def _compute_actual_cost(self):
if self.state == 'done':
self.actual_cost = self.estimate_cost * 1.18

@api.depends('technician_id', 'state')
def _compute_state_after_assigned(self):
if self.state == 'new' and self.technician_id:
self.state = 'assigned'
self.current_stage = 'assigned'
else:
self.current_stage = self.state
131 changes: 131 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
from odoo import api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools.float_utils import float_compare


class EstateProperty(models.Model):
_name = "estate.property"
_description = "Test Model for real estate"
_check_positive_expected_price = models.Constraint("CHECK (expected_price >= 0)","expected_price should be positive")
_check_positive_selling_price = models.Constraint("CHECK (selling_price >= 0)","selling_price should be positive")
_order="id desc"

name = fields.Char(default="Unknown")
last_seen = fields.Datetime("Last Seen", default=fields.Datetime.now)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(
copy=False, default=fields.Date.add(fields.Date.today(), months=3)
)
expected_price = fields.Float()
selling_price = fields.Float(copy=False, readonly=True, default=0)
bedrooms = fields.Integer(default=2)
living_area = fields.Integer()
facades = fields.Integer()
garage = fields.Boolean()
garden = fields.Boolean()
state = fields.Selection(
string="status",
selection=[
('new', "New"),
('offer received', "Offer Received"),
('offer accepted', "Offer Accepted"),
('sold', "Sold"),
('cancelled', "Cancelled"),
],
default='new',
compute="_compute_statusbar",
store=True
)
active = fields.Boolean(default=True)
garden_area = fields.Integer()
garden_orientation = fields.Selection(
string="garden orientation direction",
selection=[
('north', "North"),
('south', "South"),
('east', "East"),
('west', "West"),
],
)
property_type_id = fields.Many2one("estate.property.type")
salesperson_id = fields.Many2one(
'res.users',
string='Salesperson',
index=True,
default=lambda self: self.env.user,
)
buyer_id = fields.Many2one('res.partner', default='None', copy=False)
tag_ids = fields.Many2many('estate.property.tag')
offer_ids = fields.One2many('estate.property.offer', 'property_id', string='offers')
total_area = fields.Float(compute='_compute_total_area')
max_offer_price = fields.Float(default=None, compute="_compute_max_offer_price", store=True)
estate_maintainance_id = fields.One2many(
'estate.maintainance.request', 'property_id'
)
visit_ids = fields.One2many("estate.property.visit","property_id",string="visits")


@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.onchange('garden')
def _onchange_garden(self):
if self.garden:
self.garden_area = 10
self.garden_orientation = "north"
else:
self.garden_area = None
self.garden_orientation = None

def button_cancel(self):
if self.state == "cancelled":
raise UserError("The property is already cancelled")
elif self.state == "sold":
raise UserError("The property is already sold, you cannot cancel it")
else:
self.state = "cancelled"

def button_sold(self):
if self.state == "sold":
raise UserError("The property is already sold")
elif self.state == "cancelled":
raise UserError("The property is already cancelled, and cannot be sold")
else:
self.state = "sold"

@api.depends('offer_ids.price')
def _compute_max_offer_price(self):
if self.offer_ids:
new_max_offer_price = max(self.offer_ids.mapped('price'))
if self.max_offer_price != new_max_offer_price:
self.max_offer_price = new_max_offer_price

def accept_best_offer(self):
for offers in self.offer_ids:
if self.max_offer_price == offers.price:
offers.action_accept_offer()
##################################
# old code
# offers.status = 'accepted'
# self.state = 'offer accepted'
# self.buyer_id = offers.partner_id
# self.selling_price = offers.price
##################################
else:
offers.status = 'refused'

@api.depends('offer_ids')
def _compute_statusbar(self):
for record in self:
if record.offer_ids and record.state == 'new':
record.state = "offer received"

@api.constrains('selling_price','expected_price')
def check_percentage(self):
for record in self:
if record.selling_price and record.expected_price:
if float_compare(record.selling_price,record.expected_price * 0.9,precision_digits=1) < 0:
raise ValidationError("the selling perice cant be less than 90% of the expected price")
68 changes: 68 additions & 0 deletions estate/models/estate_property_offer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from odoo import fields, models, api
from odoo.exceptions import UserError


class EstatePropertyOffer(models.Model):
_name = 'estate.property.offer'
_description = 'estate property offer'
_check_positive_offer_price = models.Constraint("CHECK (price >= 0)","price should be positive")
_order = "price desc"

price = fields.Float(copy=False)
status = fields.Selection(
copy=False,
string="status",
selection=[('accepted', "Accepted"), ('refused', "Refused")],
)
partner_id = fields.Many2one(
'res.partner', required=True, default=lambda self: self.env.user.partner_id.id
)
property_id = fields.Many2one('estate.property', required=True)
validity = fields.Integer(default=7, copy=False)
deadline = fields.Date(
compute='_compute_deadline', store=True, inverse='_inverse_deadline'
)

property_type_id = fields.Many2one('estate.property.type',
related="property_id.property_type_id",
store=True
)

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

def _inverse_deadline(self):
for record in self:
today_date = fields.Date.today()
record.validity = (record.deadline - today_date).days

def action_accept_offer(self):
for offer in self:
for offer.property_id in offer.property_id:
if offer.property_id.state == 'offer accepted' or offer.property_id.state == 'sold':
raise UserError("An offer has already been accepted for this property.")
else:
offer.status = 'accepted'
offer.property_id.state = 'offer accepted'
offer.property_id.buyer_id = offer.partner_id
offer.property_id.selling_price = offer.price
for offers in self.property_id.offer_ids:
if offers != self:
offers.status = 'refused'

def action_reject_offer(self):
for offer in self:
if offer.status == 'accepted':
if offer.property_id.state == 'sold':
raise UserError("the property is sold. CANT REJECT THE OFFER - THANK YOU")
else:
offer.status = 'refused'
offer.property_id.state = 'offer received'
offer.property_id.buyer_id = False
offer.property_id.selling_price = 0
elif offer.status == 'refused':
raise UserError("This offer has already been refused.")
else:
offer.status = 'refused'
11 changes: 11 additions & 0 deletions estate/models/estate_property_tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import fields, models


class EstatePropertytTag(models.Model):
_name = 'estate.property.tag'
_description = 'Estate_property_tag'
_unique_tag = models.UniqueIndex('(name)','The name of the tag must be unique')
_order = "name asc"

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


class EstatePropertyType(models.Model):
_name = 'estate.property.type'
_description = 'Estate_property_type'
_unique_type = models.UniqueIndex("(name)",'property type should be unique')
_order = "sequence"

name = fields.Char(required=True)
sequence = fields.Integer(default=1, help="Used to order the property types. Lower is better.")
offer_ids = fields.One2many("estate.property.offer", "property_type_id")
offer_count = fields.Integer(default=0,compute="_compute_total_count", store=True)
property_ids = fields.One2many('estate.property','property_type_id')

@api.depends('offer_ids')
def _compute_total_count(self):
for rec in self:
rec.offer_count = len(rec.offer_ids.mapped('id'))

31 changes: 31 additions & 0 deletions estate/models/estate_property_visit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from odoo import api, fields, models
from odoo.exceptions import UserError,ValidationError

class EstatePropertyVisit(models.Model):
_name = 'estate.property.visit'
_description = 'estate property visit'
_order = "date desc"
_sql_const = models.UniqueIndex("(date, property_id)", "This property is already booked for a visit on this date!")


property_id = fields.Many2one('estate.property', required=True)
visitor_id = fields.Many2one('res.partner', required=True)
date = fields.Date(required=True)
comment = fields.Text()
state = fields.Selection(
string="status",
selection=[
('new', "New"),
('scheduled', "Scheduled"),
('done', "Done"),
('cancelled', "Cancelled"),
],
default='new',
)
@api.constrains("date")
def _compute_date_clash(self):
for rec in self:
if rec.date < fields.Date.today():
raise ValidationError("The visit date cannot be in the past.")
if rec.date:
rec.state = "scheduled"
8 changes: 8 additions & 0 deletions estate/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
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
access_estate_maintainance_request,access_estate_maintainance_request,model_estate_maintainance_request,base.group_user,1,1,1,1
access_estate_property_visit,access_estate_property_visit,model_estate_property_visit,base.group_user,1,1,1,1
access_estate_property_manager,estate_property_manager,model_estate_property,,1,1,1,1
Loading