Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
df8d524
[ADD] estate: create the app
jugurtha-gaci Oct 20, 2025
b84e916
[ADD] estate: chapter 3
jugurtha-gaci Oct 20, 2025
c3c8c66
[ADD] estate: define access rights (chapter 4)
jugurtha-gaci Oct 20, 2025
223bbb6
[ADD] estate: create the actions and views (chapter 5) && [FIX] fix P…
jugurtha-gaci Oct 21, 2025
c7523f7
[ADD] estate: customize the view (chapter 6)
jugurtha-gaci Oct 21, 2025
094f139
[IMP] estate: Add notes.
Mathilde411 Oct 21, 2025
a9e95fd
[ADD] estate: create relationship tables : offers, tags, types - (cha…
jugurtha-gaci Oct 22, 2025
ed6ba56
[ADD] estate: implement compute, inverse, and onchange methods && [IM…
jugurtha-gaci Oct 22, 2025
7b1a091
[ADD] estate: add actions (chapter 9) && [FIX] estate: fix runbot errors
jugurtha-gaci Oct 22, 2025
21a1729
[FIX] fix runbot errors
jugurtha-gaci Oct 22, 2025
0b46628
[FIX] fix runbot errors
jugurtha-gaci Oct 22, 2025
c8c8d80
[FIX] fix runbot errors
jugurtha-gaci Oct 22, 2025
491c499
[ADD] estate: add constraints (chapter 10)
jugurtha-gaci Oct 22, 2025
e165dc8
[ADD] estate: chapter 11 & 12
jugurtha-gaci Oct 24, 2025
e0ef55f
[IMP] fix runbot errors
jugurtha-gaci Oct 24, 2025
7e1411d
[FIX] fix runbot errors
jugurtha-gaci Oct 24, 2025
02054ed
[FIX] estate: fix runbot errors
jugurtha-gaci Oct 24, 2025
5fc2e02
[ADD] estate: create invoices for sold properties (chapter 13)
jugurtha-gaci Oct 24, 2025
f6db4a4
[IMP] estate: add the kanban view for the estate properties (chapter 14)
jugurtha-gaci Oct 24, 2025
3d7c3b6
[IMP] estate: refactoring
jugurtha-gaci Oct 27, 2025
1e70ae4
[FIX] estate: fix runbot errors
jugurtha-gaci Oct 27, 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
17 changes: 17 additions & 0 deletions estate/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
'name': "Estate",
'version': '1.0',
'depends': ['base'],
'author': "GACI Jugurtha (jugac)",
'application': True,
'license': 'LGPL-3',
'data': [
'security/ir.model.access.csv',
'views/estate_property_views.xml',
'views/estate_property_type_views.xml',
'views/estate_property_tag_views.xml',
'views/estate_property_offer_views.xml',
'views/estate_menus.xml',
'views/res_users_views.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
116 changes: 116 additions & 0 deletions estate/models/estate_property.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from odoo import models, api
from odoo.fields import Char, Text, Html, Float, Integer, Date, Boolean, Selection, Many2many, Many2one, One2many
from odoo.tools import float_compare, float_is_zero
from odoo.exceptions import UserError, ValidationError


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

name = Char(required=True)
description = Text()
notes = Html()
postcode = Char()
date_availability = Date(
string="Available From",
copy=False,
default=lambda self: Date.add(Date.today(), months=3)
)

expected_price = Float(required=True)
selling_price = Float(copy=False)
best_offer = Float(copy=False, compute="_compute_best_offer")

bedrooms = Integer(default=2)
living_area = Integer(string="Living Area (sqm)")
total_area = Integer(
compute="_compute_total_area",
store=True,
string="Total Area (sqm)",
)
facades = Integer()
garage = Boolean()

garden = Boolean()
garden_area = Integer()
garden_orientation = Selection(
selection=[
('north', 'North'),
('south', 'South'),
('east', 'East'),
('west', 'West')
]
)

active = Boolean(default=True)
state = Selection(
selection=[
('new', 'New'),
('received', 'Offer Received'),
('accepted', 'Offer Accepted'),
('sold', 'Sold'),
('canceled', 'Canceled')
],
default="new"
)

# relations
property_type_id = Many2one("estate.property.type")
buyer_id = Many2one("res.partner")
salesman_id = Many2one("res.users", default=lambda self: self.env.user)
tag_ids = Many2many("estate.property.tag")
offer_ids = One2many("estate.property.offer", "property_id")

@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_offer(self):
for record in self:
record.best_offer = max((offer.price for offer in record.offer_ids), default=0)

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

def action_set_sold(self):
for record in self:
if record.state == 'canceled':
raise UserError('A canceled property cannot be sold')
record.state = 'sold'
return True

def action_set_canceled(self):
for record in self:
if record.state == 'sold':
raise UserError('A sold property cannot be canceled')
record.state = 'canceled'
return True

_check_expected_price = models.Constraint(
'CHECK(expected_price >= 0)',
'The expected price of the property should be strictly postitive',
)

@api.constrains('selling_price')
def _check_selling_price(self):
for record in self.filtered(lambda p: p.state not in ("new", "received")):

if float_compare(record.selling_price, (record.expected_price * 0.9), 2) < 0:
raise ValidationError("The selling price cannot be lower than 90%% of the expected price")

if float_is_zero(record.selling_price, 2):
raise ValidationError("The selling price should be positive")

@api.ondelete(at_uninstall=False)
def _unlink_property(self):
if self.state not in ('new', 'canceled'):
raise UserError("This property can't be deleted")

return super().unlink()
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, api
from odoo.fields import Float, Selection, Integer, Date, Many2one
from odoo.exceptions import UserError


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

price = Float(required=True)
status = Selection(
selection=[
('new', 'New'),
('refused', 'Refused'),
('accepted', 'Accepted')
],
default="new",
copy=False
)
validity = Integer(default=7)
create_date = Date(default=lambda self: Date.today(), readonly=True)
date_deadline = Date(compute="_compute_date_deadline", inverse="_inverse_validity")

# relations
partner_id = Many2one("res.partner", required=True)
property_id = Many2one("estate.property", required=True, ondelete="cascade")
property_type_id = Many2one(
related="property_id.property_type_id",
store=True,
)

@api.depends("validity", "create_date")
def _compute_date_deadline(self):
for record in self:
record.date_deadline = Date.add(record.create_date, days=record.validity)

@api.depends("date_deadline")
def _inverse_validity(self):
for record in self:
if record.date_deadline:
record.validity = (record.date_deadline - record.create_date).days

def action_accept(self):
for record in self:
record.status = 'accepted'
record.property_id.buyer_id = record.partner_id
record.property_id.selling_price = record.price
record.property_id.state = 'accepted'

return True

def action_refuse(self):
self.status = 'refused'
return True

@api.model_create_multi
def create(self, vals_list):

for offer in self:
if offer.price > vals_list.price:
raise UserError('The offer price should be greater than those already received')

res = super().create(vals_list)

if res.property_id.state != 'received':
res.property_id.state = 'received'

return res

_check_selling_price = models.Constraint(
'CHECK(price > 0)',
'The offer price should be strictly postitive',
)
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 models
from odoo.fields import Char, Integer


class EstatePropertyTag(models.Model):
_name = "estate.property.tag"
_description = "Estate tags"
_order = "name"

name = Char(required=True)
color = Integer()

_check_name_is_unique = models.Constraint(
'unique(name)',
'The tag name should be unique',
)
21 changes: 21 additions & 0 deletions estate/models/estate_property_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from odoo import models, api
from odoo.fields import Char, Integer, One2many


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

name = Char(required=True)
sequence = Integer('Sequence', default=1)

# relations
property_ids = One2many("estate.property", "property_type_id")
offer_ids = One2many("estate.property.offer", "property_type_id")
offer_count = Integer(default=0, compute="_compute_offer_count")

@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
from odoo.fields import One2many


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

property_ids = One2many(
comodel_name='estate.property',
inverse_name='salesman_id',
domain="[('state', 'not in', ('sold', 'canceled'))]"
)
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_group_user,estate.group.user,model_estate_property,base.group_user,1,1,1,1
access_estate_type_group_user,estate.type.group.user,model_estate_property_type,base.group_user,1,1,1,1
access_estate_tag_group_user,estate.tag.group.user,model_estate_property_tag,base.group_user,1,1,1,1
access_estate_offer_group_user,estate.offer.group.user,model_estate_property_offer,base.group_user,1,1,1,1
17 changes: 17 additions & 0 deletions estate/views/estate_menus.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem id="estate_property_menu_root" name="Real Estate">

<menuitem id="estate_property_menu_home" name="Advertisements">
<menuitem id="estate_property_menu_action" action="estate_property_action"/>
</menuitem>

<menuitem id="estate_property_menu_settings" name="Settings">
<menuitem id="estate_property_type_menu_action" action="estate_property_type_action"/>
<menuitem id="estate_property_tag_menu_action" action="estate_property_tag_action"/>
</menuitem>

</menuitem>
</data>
</odoo>
20 changes: 20 additions & 0 deletions estate/views/estate_property_offer_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="estate_property_offer_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="Estate offer">
<group>
<field name="price"/>
<field name="partner_id"/>
<field name="status"/>
<field name="validity"/>
<field name="date_deadline"/>
</group>
</form>
</field>
</record>
</data>
</odoo>
30 changes: 30 additions & 0 deletions estate/views/estate_property_tag_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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_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="Estate tag" editable="top">
<field name="name"/>
</list>
</field>
</record>

<record id="estate_property_tag_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 string="Estate tag">
<field name="name"/>
</form>
</field>
</record>
</data>
</odoo>
Loading